From a7f64f0b39cb7141f16608c171b20bee452fd024 Mon Sep 17 00:00:00 2001
From: Ben Longbons <b.r.longbons@gmail.com>
Date: Wed, 23 Jul 2014 23:55:41 -0700
Subject: In magic, use Variant for all the old anonymous nested unions

---
 src/map/magic-expr-eval.hpp        |   30 +-
 src/map/magic-expr.cpp             | 1080 ++++++++++++++++++++----------------
 src/map/magic-expr.hpp             |    4 +-
 src/map/magic-interpreter-base.cpp |  151 +++--
 src/map/magic-interpreter-base.hpp |    4 +-
 src/map/magic-interpreter.hpp      |  580 ++++++++++++-------
 src/map/magic-interpreter.t.hpp    |   70 ---
 src/map/magic-stmt.cpp             |  512 +++++++----------
 src/map/magic-v2.cpp               |  255 ++++-----
 src/map/magic.cpp                  |    9 +-
 10 files changed, 1385 insertions(+), 1310 deletions(-)

(limited to 'src/map')

diff --git a/src/map/magic-expr-eval.hpp b/src/map/magic-expr-eval.hpp
index 3cbc8f5..4529c04 100644
--- a/src/map/magic-expr-eval.hpp
+++ b/src/map/magic-expr-eval.hpp
@@ -31,31 +31,21 @@ namespace tmwa
 namespace magic
 {
 // TODO soon kill this unlike I killed VAR
-#define ARGINT(x) args[x].v.v_int
-#define ARGDIR(x) args[x].v.v_dir
-#define ARGSTR(x) ZString(args[x].v.v_string)
-#define ARGENTITY(x) args[x].v.v_entity
-#define ARGLOCATION(x) args[x].v.v_location
-#define ARGAREA(x) args[x].v.v_area
-#define ARGSPELL(x) args[x].v.v_spell
-#define ARGINVOCATION(x) args[x].v.v_invocation
-
-#define RESULTINT result->v.v_int
-#define RESULTDIR result->v.v_dir
-#define RESULTSTR result->v.v_string
-#define RESULTENTITY result->v.v_entity
-#define RESULTLOCATION result->v.v_location
-#define RESULTAREA result->v.v_area
-#define RESULTSPELL result->v.v_spell
-#define RESULTINVOCATION result->v.v_invocation
-
-#define ARG_TYPE(x) args[x].ty
+#define ARGINT(x) args[x].get_if<ValInt>()->v_int
+#define ARGDIR(x) args[x].get_if<ValDir>()->v_dir
+#define ARGSTR(x) ZString(args[x].get_if<ValString>()->v_string)
+#define ARGENTITY(x) args[x].get_if<ValEntityPtr>()->v_entity
+#define ARGLOCATION(x) args[x].get_if<ValLocation>()->v_location
+#define ARGAREA(x) args[x].get_if<ValArea>()->v_area
+#define ARGSPELL(x) args[x].get_if<ValSpell>()->v_spell
+#define ARGINVOCATION(x) args[x].get_if<ValInvocationPtr>()->v_invocation
+
 #define ENTITY_TYPE(x) ARGENTITY(x)->bl_type
 
 #define ARGPC(x)  (ARGENTITY(x)->is_player())
 #define ARGNPC(x)  (ARGENTITY(x)->is_npc())
 #define ARGMOB(x)  (ARGENTITY(x)->is_mob())
 
-#define ARG_MAY_BE_AREA(x) (ARG_TYPE(x) == TYPE::AREA || ARG_TYPE(x) == TYPE::LOCATION)
+#define ARG_MAY_BE_AREA(x) (args[x].is<ValArea>() || args[x].is<ValArea>())
 } // namespace magic
 } // namespace tmwa
diff --git a/src/map/magic-expr.cpp b/src/map/magic-expr.cpp
index c0551d2..306126e 100644
--- a/src/map/magic-expr.cpp
+++ b/src/map/magic-expr.cpp
@@ -50,26 +50,19 @@ namespace tmwa
 {
 namespace magic
 {
-template<class T>
-bool CHECK_TYPE(T *v, TYPE t)
-{
-    return v->ty == t;
-}
-
 static
 void free_area(dumb_ptr<area_t> area)
 {
     if (!area)
         return;
 
-    switch (area->ty)
+    MATCH (*area)
     {
-        case AREA::UNION:
-            free_area(area->a.a_union[0]);
-            free_area(area->a.a_union[1]);
-            break;
-        default:
-            break;
+        CASE (const AreaUnion&, a)
+        {
+            free_area(a.a_union[0]);
+            free_area(a.a_union[1]);
+        }
     }
 
     area.delete_();
@@ -78,52 +71,108 @@ void free_area(dumb_ptr<area_t> area)
 static
 dumb_ptr<area_t> dup_area(dumb_ptr<area_t> area)
 {
-    dumb_ptr<area_t> retval = dumb_ptr<area_t>::make();
-    *retval = *area;
-
-    switch (area->ty)
+    MATCH (*area)
     {
-        case AREA::UNION:
-            retval->a.a_union[0] = dup_area(retval->a.a_union[0]);
-            retval->a.a_union[1] = dup_area(retval->a.a_union[1]);
-            break;
-        default:
-            break;
+        CASE (const location_t&, loc)
+        {
+            return dumb_ptr<area_t>::make(loc, area->size);
+        }
+        CASE (const AreaUnion&, a)
+        {
+            AreaUnion u;
+            u.a_union[0] = dup_area(a.a_union[0]);
+            u.a_union[1] = dup_area(a.a_union[1]);
+            return dumb_ptr<area_t>::make(u, area->size);
+        }
+        CASE (const AreaRect&, rect)
+        {
+            return dumb_ptr<area_t>::make(rect, area->size);
+        }
+        CASE (const AreaBar&, bar)
+        {
+            return dumb_ptr<area_t>::make(bar, area->size);
+        }
     }
 
-    return retval;
+    abort();
 }
 
-void magic_copy_var(val_t *dest, val_t *src)
+void magic_copy_var(val_t *dest, const val_t *src)
 {
-    *dest = *src;
-
-    switch (dest->ty)
+    MATCH (*src)
     {
-        case TYPE::STRING:
-            dest->v.v_string = dest->v.v_string.dup();
-            break;
-        case TYPE::AREA:
-            dest->v.v_area = dup_area(dest->v.v_area);
-            break;
+        // mumble mumble not a public API ...
         default:
-            break;
+        {
+            abort();
+        }
+        CASE (const ValUndef&, s)
+        {
+            *dest = s;
+        }
+        CASE (const ValInt&, s)
+        {
+            *dest = s;
+        }
+        CASE (const ValDir&, s)
+        {
+            *dest = s;
+        }
+        CASE (const ValString&, s)
+        {
+            *dest = ValString{s.v_string.dup()};
+        }
+        CASE (const ValEntityInt&, s)
+        {
+            *dest = s;
+        }
+        CASE (const ValEntityPtr&, s)
+        {
+            *dest = s;
+        }
+        CASE (const ValLocation&, s)
+        {
+            *dest = s;
+        }
+        CASE (const ValArea&, s)
+        {
+            *dest = ValArea{dup_area(s.v_area)};
+        }
+        CASE (const ValSpell&, s)
+        {
+            *dest = s;
+        }
+        CASE (const ValInvocationInt&, s)
+        {
+            *dest = s;
+        }
+        CASE (const ValInvocationPtr&, s)
+        {
+            *dest = s;
+        }
+        CASE (const ValFail&, s)
+        {
+            *dest = s;
+        }
+        CASE (const ValNegative1&, s)
+        {
+            *dest = s;
+        }
     }
-
 }
 
 void magic_clear_var(val_t *v)
 {
-    switch (v->ty)
+    MATCH (*v)
     {
-        case TYPE::STRING:
-            v->v.v_string.delete_();
-            break;
-        case TYPE::AREA:
-            free_area(v->v.v_area);
-            break;
-        default:
-            break;
+        CASE (ValString&, s)
+        {
+            s.v_string.delete_();
+        }
+        CASE (const ValArea&, a)
+        {
+            free_area(a.v_area);
+        }
     }
 }
 
@@ -152,7 +201,7 @@ AString show_entity(dumb_ptr<block_list> entity)
 }
 
 static
-void stringify(val_t *v, int within_op)
+void stringify(val_t *v)
 {
     static earray<LString, DIR, DIR::COUNT> dirs //=
     {{
@@ -163,89 +212,86 @@ void stringify(val_t *v, int within_op)
     }};
     AString buf;
 
-    switch (v->ty)
+    MATCH (*v)
     {
-        case TYPE::UNDEF:
+        default:
+        {
+            abort();
+        }
+        CASE (const ValUndef&, x)
+        {
+            (void)x;
             buf = "UNDEF"_s;
-            break;
-
-        case TYPE::INT:
-            buf = STRPRINTF("%i"_fmt, v->v.v_int);
-            break;
-
-        case TYPE::STRING:
+        }
+        CASE (const ValInt&, x)
+        {
+            buf = STRPRINTF("%i"_fmt, x.v_int);
+        }
+        CASE (const ValString&, x)
+        {
+            (void)x;
             return;
-
-        case TYPE::DIR:
-            buf = dirs[v->v.v_dir];
-            break;
-
-        case TYPE::ENTITY:
-            buf = show_entity(v->v.v_entity);
-            break;
-
-        case TYPE::LOCATION:
+        }
+        CASE (const ValDir&, x)
+        {
+            buf = dirs[x.v_dir];
+        }
+        CASE (const ValEntityPtr&, x)
+        {
+            buf = show_entity(x.v_entity);
+        }
+        CASE (const ValLocation&, x)
+        {
             buf = STRPRINTF("<\"%s\", %d, %d>"_fmt,
-                    v->v.v_location.m->name_,
-                    v->v.v_location.x,
-                    v->v.v_location.y);
-            break;
-
-        case TYPE::AREA:
+                    x.v_location.m->name_,
+                    x.v_location.x,
+                    x.v_location.y);
+        }
+        CASE (const ValArea&, x)
+        {
             buf = "%area"_s;
-            free_area(v->v.v_area);
-            break;
-
-        case TYPE::SPELL:
-            buf = v->v.v_spell->name;
+            free_area(x.v_area);
+        }
+        CASE (const ValSpell&, x)
+        {
+            buf = x.v_spell->name;
             break;
-
-        case TYPE::INVOCATION:
+        }
+        CASE (const ValInvocationInt&, x)
         {
-            dumb_ptr<invocation> invocation_ = within_op
-                ? v->v.v_invocation
-                : map_id2bl(wrap<BlockId>(static_cast<uint32_t>(v->v.v_int)))->is_spell();
+            dumb_ptr<invocation> invocation_ =
+                map_id2bl(x.v_iid)->is_spell();
+            buf = invocation_->spell->name;
+        }
+        CASE (const ValInvocationPtr&, x)
+        {
+            dumb_ptr<invocation> invocation_ =
+                x.v_invocation;
             buf = invocation_->spell->name;
         }
-            break;
-
-        default:
-            FPRINTF(stderr, "[magic] INTERNAL ERROR: Cannot stringify %d\n"_fmt,
-                    v->ty);
-            return;
     }
 
-    v->v.v_string = dumb_string::copys(buf);
-    v->ty = TYPE::STRING;
+    *v = ValString{dumb_string::copys(buf)};
 }
 
 static
 void intify(val_t *v)
 {
-    if (v->ty == TYPE::INT)
+    if (v->is<ValInt>())
         return;
 
     magic_clear_var(v);
-    v->ty = TYPE::INT;
-    v->v.v_int = 1;
-}
-
-static
-dumb_ptr<area_t> area_new(AREA ty)
-{
-    auto retval = dumb_ptr<area_t>::make();
-    retval->ty = ty;
-    return retval;
+    *v = ValInt{1};
 }
 
 static
 dumb_ptr<area_t> area_union(dumb_ptr<area_t> area, dumb_ptr<area_t> other_area)
 {
-    dumb_ptr<area_t> retval = area_new(AREA::UNION);
-    retval->a.a_union[0] = area;
-    retval->a.a_union[1] = other_area;
-    retval->size = area->size + other_area->size;   /* Assume no overlap */
-    return retval;
+    AreaUnion a;
+    a.a_union[0] = area;
+    a.a_union[1] = other_area;
+    int size = area->size + other_area->size;   /* Assume no overlap */
+    return dumb_ptr<area_t>::make(a, size);
 }
 
 /**
@@ -254,41 +300,43 @@ dumb_ptr<area_t> area_union(dumb_ptr<area_t> area, dumb_ptr<area_t> other_area)
 static
 void make_area(val_t *v)
 {
-    if (v->ty == TYPE::LOCATION)
+    if (ValLocation *l = v->get_if<ValLocation>())
     {
-        auto a = dumb_ptr<area_t>::make();
-        v->ty = TYPE::AREA;
-        a->ty = AREA::LOCATION;
-        a->a.a_loc = v->v.v_location;
-        v->v.v_area = a;
+        auto a = dumb_ptr<area_t>::make(l->v_location, 1);
+        *v = ValArea{a};
     }
 }
 
 static
 void make_location(val_t *v)
 {
-    if (v->ty == TYPE::AREA && v->v.v_area->ty == AREA::LOCATION)
+    if (ValArea *a = v->get_if<ValArea>())
     {
-        location_t location = v->v.v_area->a.a_loc;
-        free_area(v->v.v_area);
-        v->ty = TYPE::LOCATION;
-        v->v.v_location = location;
+        MATCH (*a->v_area)
+        {
+            CASE (const location_t&, location)
+            {
+                free_area(a->v_area);
+                *v = ValLocation{location};
+            }
+        }
     }
 }
 
 static
 void make_spell(val_t *v)
 {
-    if (v->ty == TYPE::INVOCATION)
+    assert(!v->is<ValInvocationInt>());
+    if (ValInvocationPtr *p = v->get_if<ValInvocationPtr>())
     {
-        dumb_ptr<invocation> invoc = v->v.v_invocation;
-        //invoc = (dumb_ptr<invocation>) map_id2bl(v->v.v_int);
+        dumb_ptr<invocation> invoc = p->v_invocation;
         if (!invoc)
-            v->ty = TYPE::FAIL;
+        {
+            *v = ValFail{};
+        }
         else
         {
-            v->ty = TYPE::SPELL;
-            v->v.v_spell = invoc->spell;
+            *v = ValSpell{invoc->spell};
         }
     }
 }
@@ -296,34 +344,31 @@ void make_spell(val_t *v)
 static
 int fun_add(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    if (ARG_TYPE(0) == TYPE::INT && ARG_TYPE(1) == TYPE::INT)
+    if (args[0].is<ValInt>() && args[1].is<ValInt>())
     {
         /* Integer addition */
-        RESULTINT = ARGINT(0) + ARGINT(1);
-        result->ty = TYPE::INT;
+        *result = ValInt{ARGINT(0) + ARGINT(1)};
     }
     else if (ARG_MAY_BE_AREA(0) && ARG_MAY_BE_AREA(1))
     {
         /* Area union */
         make_area(&args[0]);
         make_area(&args[1]);
-        RESULTAREA = area_union(ARGAREA(0), ARGAREA(1));
-        ARGAREA(0) = nullptr;
-        ARGAREA(1) = nullptr;
-        result->ty = TYPE::AREA;
+        *result = ValArea{area_union(ARGAREA(0), ARGAREA(1))};
+        ARGAREA(0) = nullptr; args[0] = ValUndef{};
+        ARGAREA(1) = nullptr; args[1] = ValUndef{};
     }
     else
     {
         /* Anything else -> string concatenation */
-        stringify(&args[0], 1);
-        stringify(&args[1], 1);
+        stringify(&args[0]);
+        stringify(&args[1]);
         /* Yes, we could speed this up. */
         // ugh
         MString m;
         m += ARGSTR(0);
         m += ARGSTR(1);
-        RESULTSTR = dumb_string::copys(AString(m));
-        result->ty = TYPE::STRING;
+        *result = ValString{dumb_string::copys(AString(m))};
     }
     return 0;
 }
@@ -331,14 +376,14 @@ int fun_add(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 static
 int fun_sub(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = ARGINT(0) - ARGINT(1);
+    *result = ValInt{ARGINT(0) - ARGINT(1)};
     return 0;
 }
 
 static
 int fun_mul(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = ARGINT(0) * ARGINT(1);
+    *result = ValInt{ARGINT(0) * ARGINT(1)};
     return 0;
 }
 
@@ -347,7 +392,7 @@ int fun_div(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
     if (!ARGINT(1))
         return 1;               /* division by zero */
-    RESULTINT = ARGINT(0) / ARGINT(1);
+    *result = ValInt{ARGINT(0) / ARGINT(1)};
     return 0;
 }
 
@@ -356,52 +401,52 @@ int fun_mod(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
     if (!ARGINT(1))
         return 1;               /* division by zero */
-    RESULTINT = ARGINT(0) % ARGINT(1);
+    *result = ValInt{ARGINT(0) % ARGINT(1)};
     return 0;
 }
 
 static
 int fun_or(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = ARGINT(0) || ARGINT(1);
+    *result = ValInt{ARGINT(0) || ARGINT(1)};
     return 0;
 }
 
 static
 int fun_and(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = ARGINT(0) && ARGINT(1);
+    *result = ValInt{ARGINT(0) && ARGINT(1)};
     return 0;
 }
 
 static
 int fun_not(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = !ARGINT(0);
+    *result = ValInt{!ARGINT(0)};
     return 0;
 }
 
 static
 int fun_neg(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = ~ARGINT(0);
+    *result = ValInt{~ARGINT(0)};
     return 0;
 }
 
 static
 int fun_gte(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    if (ARG_TYPE(0) == TYPE::STRING || ARG_TYPE(1) == TYPE::STRING)
+    if (args[0].is<ValString>() || args[1].is<ValString>())
     {
-        stringify(&args[0], 1);
-        stringify(&args[1], 1);
-        RESULTINT = ARGSTR(0) >= ARGSTR(1);
+        stringify(&args[0]);
+        stringify(&args[1]);
+        *result = ValInt{ARGSTR(0) >= ARGSTR(1)};
     }
     else
     {
         intify(&args[0]);
         intify(&args[1]);
-        RESULTINT = ARGINT(0) >= ARGINT(1);
+        *result = ValInt{ARGINT(0) >= ARGINT(1)};
     }
     return 0;
 }
@@ -410,24 +455,24 @@ static
 int fun_lt(dumb_ptr<env_t> env, val_t *result, Slice<val_t> args)
 {
     fun_gte(env, result, args);
-    RESULTINT = !RESULTINT;
+    result->get_if<ValInt>()->v_int ^= 1;
     return 0;
 }
 
 static
 int fun_gt(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    if (ARG_TYPE(0) == TYPE::STRING || ARG_TYPE(1) == TYPE::STRING)
+    if (args[0].is<ValString>() || args[1].is<ValString>())
     {
-        stringify(&args[0], 1);
-        stringify(&args[1], 1);
-        RESULTINT = ARGSTR(0) > ARGSTR(1);
+        stringify(&args[0]);
+        stringify(&args[1]);
+        *result = ValInt{ARGSTR(0) > ARGSTR(1)};
     }
     else
     {
         intify(&args[0]);
         intify(&args[1]);
-        RESULTINT = ARGINT(0) > ARGINT(1);
+        *result = ValInt{ARGINT(0) > ARGINT(1)};
     }
     return 0;
 }
@@ -436,38 +481,38 @@ static
 int fun_lte(dumb_ptr<env_t> env, val_t *result, Slice<val_t> args)
 {
     fun_gt(env, result, args);
-    RESULTINT = !RESULTINT;
+    result->get_if<ValInt>()->v_int ^= 1;
     return 0;
 }
 
 static
 int fun_eq(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    if (ARG_TYPE(0) == TYPE::STRING || ARG_TYPE(1) == TYPE::STRING)
+    if (args[0].is<ValString>() || args[1].is<ValString>())
     {
-        stringify(&args[0], 1);
-        stringify(&args[1], 1);
-        RESULTINT = ARGSTR(0) == ARGSTR(1);
+        stringify(&args[0]);
+        stringify(&args[1]);
+        *result = ValInt{ARGSTR(0) == ARGSTR(1)};
     }
-    else if (ARG_TYPE(0) == TYPE::DIR && ARG_TYPE(1) == TYPE::DIR)
-        RESULTINT = ARGDIR(0) == ARGDIR(1);
-    else if (ARG_TYPE(0) == TYPE::ENTITY && ARG_TYPE(1) == TYPE::ENTITY)
-        RESULTINT = ARGENTITY(0) == ARGENTITY(1);
-    else if (ARG_TYPE(0) == TYPE::LOCATION && ARG_TYPE(1) == TYPE::LOCATION)
-        RESULTINT = (ARGLOCATION(0).x == ARGLOCATION(1).x
+    else if (args[0].is<ValDir>() && args[1].is<ValDir>())
+        *result = ValInt{ARGDIR(0) == ARGDIR(1)};
+    else if (args[0].is<ValEntityPtr>() && args[1].is<ValEntityPtr>())
+        *result = ValInt{ARGENTITY(0) == ARGENTITY(1)};
+    else if (args[0].is<ValLocation>() && args[1].is<ValLocation>())
+        *result = ValInt{(ARGLOCATION(0).x == ARGLOCATION(1).x
                      && ARGLOCATION(0).y == ARGLOCATION(1).y
-                     && ARGLOCATION(0).m == ARGLOCATION(1).m);
-    else if (ARG_TYPE(0) == TYPE::AREA && ARG_TYPE(1) == TYPE::AREA)
-        RESULTINT = ARGAREA(0) == ARGAREA(1); /* Probably not that great an idea... */
-    else if (ARG_TYPE(0) == TYPE::SPELL && ARG_TYPE(1) == TYPE::SPELL)
-        RESULTINT = ARGSPELL(0) == ARGSPELL(1);
-    else if (ARG_TYPE(0) == TYPE::INVOCATION && ARG_TYPE(1) == TYPE::INVOCATION)
-        RESULTINT = ARGINVOCATION(0) == ARGINVOCATION(1);
+                     && ARGLOCATION(0).m == ARGLOCATION(1).m)};
+    else if (args[0].is<ValArea>() && args[1].is<ValArea>())
+        *result = ValInt{ARGAREA(0) == ARGAREA(1)}; /* Probably not that great an idea... */
+    else if (args[0].is<ValSpell>() && args[1].is<ValSpell>())
+        *result = ValInt{ARGSPELL(0) == ARGSPELL(1)};
+    else if (args[0].is<ValInvocationPtr>() && args[1].is<ValInvocationPtr>())
+        *result = ValInt{ARGINVOCATION(0) == ARGINVOCATION(1)};
     else
     {
         intify(&args[0]);
         intify(&args[1]);
-        RESULTINT = ARGINT(0) == ARGINT(1);
+        *result = ValInt{ARGINT(0) == ARGINT(1)};
     }
     return 0;
 }
@@ -476,56 +521,56 @@ static
 int fun_ne(dumb_ptr<env_t> env, val_t *result, Slice<val_t> args)
 {
     fun_eq(env, result, args);
-    RESULTINT = !RESULTINT;
+    result->get_if<ValInt>()->v_int ^= 1;
     return 0;
 }
 
 static
 int fun_bitand(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = ARGINT(0) & ARGINT(1);
+    *result = ValInt{ARGINT(0) & ARGINT(1)};
     return 0;
 }
 
 static
 int fun_bitor(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = ARGINT(0) | ARGINT(1);
+    *result = ValInt{ARGINT(0) | ARGINT(1)};
     return 0;
 }
 
 static
 int fun_bitxor(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = ARGINT(0) ^ ARGINT(1);
+    *result = ValInt{ARGINT(0) ^ ARGINT(1)};
     return 0;
 }
 
 static
 int fun_bitshl(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = ARGINT(0) << ARGINT(1);
+    *result = ValInt{ARGINT(0) << ARGINT(1)};
     return 0;
 }
 
 static
 int fun_bitshr(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = ARGINT(0) >> ARGINT(1);
+    *result = ValInt{ARGINT(0) >> ARGINT(1)};
     return 0;
 }
 
 static
 int fun_max(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = std::max(ARGINT(0), ARGINT(1));
+    *result = ValInt{std::max(ARGINT(0), ARGINT(1))};
     return 0;
 }
 
 static
 int fun_min(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = std::min(ARGINT(0), ARGINT(1));
+    *result = ValInt{std::min(ARGINT(0), ARGINT(1))};
     return 0;
 }
 
@@ -542,37 +587,38 @@ int fun_if_then_else(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 void magic_area_rect(map_local **m, int *x, int *y, int *width, int *height,
         area_t& area_)
 {
-    area_t *area = &area_; // diff hack
-    switch (area->ty)
+    MATCH (area_)
     {
-        case AREA::UNION:
-            break;
-
-        case AREA::LOCATION:
-            *m = area->a.a_loc.m;
-            *x = area->a.a_loc.x;
-            *y = area->a.a_loc.y;
+        CASE (const AreaUnion&, a)
+        {
+            (void)a;
+            abort();
+        }
+        CASE (const location_t&, a_loc)
+        {
+            *m = a_loc.m;
+            *x = a_loc.x;
+            *y = a_loc.y;
             *width = 1;
             *height = 1;
-            break;
-
-        case AREA::RECT:
-            *m = area->a.a_rect.loc.m;
-            *x = area->a.a_rect.loc.x;
-            *y = area->a.a_rect.loc.y;
-            *width = area->a.a_rect.width;
-            *height = area->a.a_rect.height;
-            break;
-
-        case AREA::BAR:
+        }
+        CASE (const AreaRect&, a_rect)
+        {
+            *m = a_rect.loc.m;
+            *x = a_rect.loc.x;
+            *y = a_rect.loc.y;
+            *width = a_rect.width;
+            *height = a_rect.height;
+        }
+        CASE (const AreaBar&, a_bar)
         {
-            int tx = area->a.a_bar.loc.x;
-            int ty = area->a.a_bar.loc.y;
-            int twidth = area->a.a_bar.width;
-            int tdepth = area->a.a_bar.width;
-            *m = area->a.a_bar.loc.m;
+            int tx = a_bar.loc.x;
+            int ty = a_bar.loc.y;
+            int twidth = a_bar.width;
+            int tdepth = a_bar.width;
+            *m = a_bar.loc.m;
 
-            switch (area->a.a_bar.dir)
+            switch (a_bar.dir)
             {
                 case DIR::S:
                     *x = tx - twidth;
@@ -609,22 +655,45 @@ void magic_area_rect(map_local **m, int *x, int *y, int *width, int *height,
                     *y = ty;
                     *width = *height = 1;
             }
-            break;
         }
     }
 }
 
 int magic_location_in_area(map_local *m, int x, int y, dumb_ptr<area_t> area)
 {
-    switch (area->ty)
+    MATCH (*area)
     {
-        case AREA::UNION:
-            return magic_location_in_area(m, x, y, area->a.a_union[0])
-                || magic_location_in_area(m, x, y, area->a.a_union[1]);
-        case AREA::LOCATION:
-        case AREA::RECT:
-        case AREA::BAR:
+        CASE (const AreaUnion&, a)
         {
+            return magic_location_in_area(m, x, y, a.a_union[0])
+                || magic_location_in_area(m, x, y, a.a_union[1]);
+        }
+        CASE (const location_t&, a_loc)
+        {
+            (void)a_loc;
+            // TODO this can be simplified
+            map_local *am;
+            int ax, ay, awidth, aheight;
+            magic_area_rect(&am, &ax, &ay, &awidth, &aheight, *area);
+            return (am == m
+                    && (x >= ax) && (y >= ay)
+                    && (x < ax + awidth) && (y < ay + aheight));
+        }
+        CASE (const AreaRect&, a_rect)
+        {
+            (void)a_rect;
+            // TODO this is too complicated
+            map_local *am;
+            int ax, ay, awidth, aheight;
+            magic_area_rect(&am, &ax, &ay, &awidth, &aheight, *area);
+            return (am == m
+                    && (x >= ax) && (y >= ay)
+                    && (x < ax + awidth) && (y < ay + aheight));
+        }
+        CASE (const AreaBar&, a_bar)
+        {
+            (void)a_bar;
+            // TODO this is wrong
             map_local *am;
             int ax, ay, awidth, aheight;
             magic_area_rect(&am, &ax, &ay, &awidth, &aheight, *area);
@@ -632,18 +701,16 @@ int magic_location_in_area(map_local *m, int x, int y, dumb_ptr<area_t> area)
                     && (x >= ax) && (y >= ay)
                     && (x < ax + awidth) && (y < ay + aheight));
         }
-        default:
-            FPRINTF(stderr, "INTERNAL ERROR: Invalid area\n"_fmt);
-            return 0;
     }
+    abort();
 }
 
 static
 int fun_is_in(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = magic_location_in_area(ARGLOCATION(0).m,
+    *result = ValInt{magic_location_in_area(ARGLOCATION(0).m,
                                         ARGLOCATION(0).x,
-                                        ARGLOCATION(0).y, ARGAREA(1));
+                                        ARGLOCATION(0).y, ARGAREA(1))};
     return 0;
 }
 
@@ -652,15 +719,16 @@ int fun_skill(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
     if (ENTITY_TYPE(0) != BL::PC
         // don't convert to enum until after the range check
+        // (actually it would be okay, I checked)
         || ARGINT(1) < 0
         || ARGINT(1) >= static_cast<uint16_t>(MAX_SKILL))
     {
-        RESULTINT = 0;
+        *result = ValInt{0};
     }
     else
     {
         SkillID id = static_cast<SkillID>(ARGINT(1));
-        RESULTINT = ARGPC(0)->status.skill[id].lv;
+        *result = ValInt{ARGPC(0)->status.skill[id].lv};
     }
     return 0;
 }
@@ -668,7 +736,7 @@ int fun_skill(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 static
 int fun_his_shroud(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = (ENTITY_TYPE(0) == BL::PC && ARGPC(0)->state.shroud_active);
+    *result = ValInt{(ENTITY_TYPE(0) == BL::PC && ARGPC(0)->state.shroud_active)};
     return 0;
 }
 
@@ -676,7 +744,7 @@ int fun_his_shroud(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 static                                                                  \
 int fun_get_##name(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)   \
 {                                                                       \
-    RESULTINT = battle_get_##name(ARGENTITY(0));                        \
+    *result = ValInt{battle_get_##name(ARGENTITY(0))};                  \
     return 0;                                                           \
 }
 
@@ -694,7 +762,7 @@ BATTLE_GETTER(max_hp)
 static
 int fun_get_dir(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTDIR = battle_get_dir(ARGENTITY(0));
+    *result = ValDir{battle_get_dir(ARGENTITY(0))};
     return 0;
 }
 
@@ -703,9 +771,9 @@ static                                                                  \
 int fun_get_##name(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)   \
 {                                                                       \
     if (ENTITY_TYPE(0) == BL::PC)                                       \
-        RESULTINT = ARGPC(0)->status.name;                              \
+        *result = ValInt{ARGPC(0)->status.name};                        \
     else                                                                \
-        RESULTINT = 0;                                                  \
+        *result = ValInt{0};                                            \
     return 0;                                                           \
 }
 
@@ -715,19 +783,19 @@ MMO_GETTER(max_sp)
 static
 int fun_name_of(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    if (ARG_TYPE(0) == TYPE::ENTITY)
+    if (args[0].is<ValEntityPtr>())
     {
-        RESULTSTR = dumb_string::copys(show_entity(ARGENTITY(0)));
+        *result = ValString{dumb_string::copys(show_entity(ARGENTITY(0)))};
         return 0;
     }
-    else if (ARG_TYPE(0) == TYPE::SPELL)
+    else if (args[0].is<ValSpell>())
     {
-        RESULTSTR = dumb_string::copys(ARGSPELL(0)->name);
+        *result = ValString{dumb_string::copys(ARGSPELL(0)->name)};
         return 0;
     }
-    else if (ARG_TYPE(0) == TYPE::INVOCATION)
+    else if (args[0].is<ValInvocationPtr>())
     {
-        RESULTSTR = dumb_string::copys(ARGINVOCATION(0)->spell->name);
+        *result = ValString{dumb_string::copys(ARGINVOCATION(0)->spell->name)};
         return 0;
     }
     return 1;
@@ -739,7 +807,7 @@ int fun_mob_id(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
     if (ENTITY_TYPE(0) != BL::MOB)
         return 1;
-    RESULTINT = unwrap<Species>(ARGMOB(0)->mob_class);
+    *result = ValInt{unwrap<Species>(ARGMOB(0)->mob_class)};
     return 0;
 }
 
@@ -762,7 +830,9 @@ void COPY_LOCATION(location_t& dest, block_list& src)
 static
 int fun_location(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    COPY_LOCATION(RESULTLOCATION, *(ARGENTITY(0)));
+    location_t loc;
+    COPY_LOCATION(loc, *(ARGENTITY(0)));
+    *result = ValLocation{loc};
     return 0;
 }
 
@@ -774,13 +844,13 @@ int fun_random(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
         delta = -delta;
     if (delta == 0)
     {
-        RESULTINT = 0;
+        *result = ValInt{0};
         return 0;
     }
-    RESULTINT = random_::to(delta);
+    *result = ValInt{random_::to(delta)};
 
     if (ARGINT(0) < 0)
-        RESULTINT = -RESULTINT;
+        result->get_if<ValInt>()->v_int *= -1;
     return 0;
 }
 
@@ -788,16 +858,16 @@ static
 int fun_random_dir(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
     if (ARGINT(0))
-        RESULTDIR = random_::choice({DIR::S, DIR::SW, DIR::W, DIR::NW, DIR::N, DIR::NE, DIR::E, DIR::SE});
+        *result = ValDir{random_::choice({DIR::S, DIR::SW, DIR::W, DIR::NW, DIR::N, DIR::NE, DIR::E, DIR::SE})};
     else
-        RESULTDIR = random_::choice({DIR::S, DIR::W, DIR::N, DIR::E});
+        *result = ValDir{random_::choice({DIR::S, DIR::W, DIR::N, DIR::E})};
     return 0;
 }
 
 static
 int fun_hash_entity(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = unwrap<BlockId>(ARGENTITY(0)->bl_id);
+    *result = ValInt{static_cast<int32_t>(unwrap<BlockId>(ARGENTITY(0)->bl_id))};
     return 0;
 }
 
@@ -807,9 +877,9 @@ int magic_find_item(Slice<val_t> args, int index, Item *item_, int *stackable)
     struct item_data *item_data;
     int must_add_sequentially;
 
-    if (ARG_TYPE(index) == TYPE::INT)
+    if (args[index].is<ValInt>())
         item_data = itemdb_exists(wrap<ItemNameId>(static_cast<uint16_t>(ARGINT(index))));
-    else if (ARG_TYPE(index) == TYPE::STRING)
+    else if (args[index].is<ValString>())
         item_data = itemdb_searchname(ARGSTR(index));
     else
         return -1;
@@ -845,7 +915,7 @@ int fun_count_item(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
     if (!chr)
         return 1;
 
-    RESULTINT = pc_count_all_items(chr, item.nameid);
+    *result = ValInt{pc_count_all_items(chr, item.nameid)};
     return 0;
 }
 
@@ -872,28 +942,28 @@ int fun_is_equipped(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
         }
     }
 
-    RESULTINT = retval;
+    *result = ValInt{retval};
     return 0;
 }
 
 static
 int fun_is_married(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = (ENTITY_TYPE(0) == BL::PC && ARGPC(0)->status.partner_id);
+    *result = ValInt{(ENTITY_TYPE(0) == BL::PC && ARGPC(0)->status.partner_id)};
     return 0;
 }
 
 static
 int fun_is_dead(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = (ENTITY_TYPE(0) == BL::PC && pc_isdead(ARGPC(0)));
+    *result = ValInt{(ENTITY_TYPE(0) == BL::PC && pc_isdead(ARGPC(0)))};
     return 0;
 }
 
 static
 int fun_is_pc(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = (ENTITY_TYPE(0) == BL::PC);
+    *result = ValInt{(ENTITY_TYPE(0) == BL::PC)};
     return 0;
 }
 
@@ -902,8 +972,8 @@ int fun_partner(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
     if (ENTITY_TYPE(0) == BL::PC && ARGPC(0)->status.partner_id)
     {
-        RESULTENTITY =
-            map_nick2sd(map_charid2nick(ARGPC(0)->status.partner_id));
+        *result =
+            ValEntityPtr{map_nick2sd(map_charid2nick(ARGPC(0)->status.partner_id))};
         return 0;
     }
     else
@@ -925,14 +995,14 @@ int fun_awayfrom(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
         loc->y += dy;
     }
 
-    RESULTLOCATION = *loc;
+    *result = ValLocation{*loc};
     return 0;
 }
 
 static
 int fun_failed(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = ARG_TYPE(0) == TYPE::FAIL;
+    *result = ValInt{args[0].is<ValFail>()};
     return 0;
 }
 
@@ -940,26 +1010,28 @@ static
 int fun_npc(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
     NpcName name = stringish<NpcName>(ARGSTR(0));
-    RESULTENTITY = npc_name2id(name);
-    return RESULTENTITY == nullptr;
+    dumb_ptr<npc_data> npc = npc_name2id(name);
+    *result = ValEntityPtr{npc};
+    return npc == nullptr;
 }
 
 static
 int fun_pc(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
     CharName name = stringish<CharName>(ARGSTR(0));
-    RESULTENTITY = map_nick2sd(name);
-    return RESULTENTITY == nullptr;
+    dumb_ptr<map_session_data> chr = map_nick2sd(name);
+    *result = ValEntityPtr{chr};
+    return chr == nullptr;
 }
 
 static
 int fun_distance(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
     if (ARGLOCATION(0).m != ARGLOCATION(1).m)
-        RESULTINT = 0x7fffffff;
+        *result = ValInt{0x7fffffff};
     else
-        RESULTINT = std::max(abs(ARGLOCATION(0).x - ARGLOCATION(1).x),
-                         abs(ARGLOCATION(0).y - ARGLOCATION(1).y));
+        *result = ValInt{std::max(abs(ARGLOCATION(0).x - ARGLOCATION(1).x),
+                         abs(ARGLOCATION(0).y - ARGLOCATION(1).y))};
     return 0;
 }
 
@@ -967,12 +1039,12 @@ static
 int fun_rdistance(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
     if (ARGLOCATION(0).m != ARGLOCATION(1).m)
-        RESULTINT = 0x7fffffff;
+        *result = ValInt{0x7fffffff};
     else
     {
         int dx = ARGLOCATION(0).x - ARGLOCATION(1).x;
         int dy = ARGLOCATION(0).y - ARGLOCATION(1).y;
-        RESULTINT = static_cast<int>(sqrt((dx * dx) + (dy * dy)));
+        *result = ValInt{static_cast<int>(sqrt((dx * dx) + (dy * dy)))};
     }
     return 0;
 }
@@ -988,7 +1060,7 @@ int fun_anchor(dumb_ptr<env_t> env, val_t *result, Slice<val_t> args)
     magic_eval(env, result, anchor->location);
 
     make_area(result);
-    if (result->ty != TYPE::AREA)
+    if (!result->is<ValArea>())
     {
         magic_clear_var(result);
         return 1;
@@ -1005,28 +1077,48 @@ int fun_line_of_sight(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
     COPY_LOCATION(e1, ARGLOCATION(0));
     COPY_LOCATION(e2, ARGLOCATION(1));
 
-    RESULTINT = battle_check_range(dumb_ptr<block_list>(&e1), dumb_ptr<block_list>(&e2), 0);
+    *result = ValInt{battle_check_range(dumb_ptr<block_list>(&e1), dumb_ptr<block_list>(&e2), 0)};
 
     return 0;
 }
 
 void magic_random_location(location_t *dest, dumb_ptr<area_t> area)
 {
-    switch (area->ty)
+    MATCH (*area)
     {
-        case AREA::UNION:
+        CASE (const AreaUnion&, a)
         {
-            if (random_::chance({area->a.a_union[0]->size, area->size}))
-                magic_random_location(dest, area->a.a_union[0]);
+            if (random_::chance({a.a_union[0]->size, area->size}))
+                magic_random_location(dest, a.a_union[0]);
             else
-                magic_random_location(dest, area->a.a_union[1]);
-            break;
+                magic_random_location(dest, a.a_union[1]);
         }
+        CASE (const location_t&, a_loc)
+        {
+            (void)a_loc;
+            // TODO this can be simplified
+            map_local *m;
+            int x, y, w, h;
+            magic_area_rect(&m, &x, &y, &w, &h, *area);
+
+            if (w <= 1)
+                w = 1;
 
-        case AREA::LOCATION:
-        case AREA::RECT:
-        case AREA::BAR:
+            if (h <= 1)
+                h = 1;
+
+            // This is not exactly the same as the old logic,
+            // but it's better.
+            auto pair = map_randfreecell(m, x, y, w, h);
+
+            dest->m = m;
+            dest->x = pair.first;
+            dest->y = pair.second;
+        }
+        CASE (const AreaRect&, a_rect)
         {
+            (void)a_rect;
+            // TODO this can be simplified
             map_local *m;
             int x, y, w, h;
             magic_area_rect(&m, &x, &y, &w, &h, *area);
@@ -1044,19 +1136,38 @@ void magic_random_location(location_t *dest, dumb_ptr<area_t> area)
             dest->m = m;
             dest->x = pair.first;
             dest->y = pair.second;
-            break;
         }
+        CASE (const AreaBar&, a_bar)
+        {
+            (void)a_bar;
+            // TODO this is wrong
+            map_local *m;
+            int x, y, w, h;
+            magic_area_rect(&m, &x, &y, &w, &h, *area);
 
-        default:
-            FPRINTF(stderr, "Unknown area type %d\n"_fmt,
-                    area->ty);
+            if (w <= 1)
+                w = 1;
+
+            if (h <= 1)
+                h = 1;
+
+            // This is not exactly the same as the old logic,
+            // but it's better.
+            auto pair = map_randfreecell(m, x, y, w, h);
+
+            dest->m = m;
+            dest->x = pair.first;
+            dest->y = pair.second;
+        }
     }
 }
 
 static
 int fun_pick_location(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    magic_random_location(&result->v.v_location, ARGAREA(0));
+    location_t loc;
+    magic_random_location(&loc, ARGAREA(0));
+    *result = ValLocation{loc};
     return 0;
 }
 
@@ -1070,7 +1181,7 @@ int fun_read_script_int(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
     if (subject_p->bl_type != BL::PC)
         return 1;
 
-    RESULTINT = get_script_var_i(subject_p->is_player(), var_name, array_index);
+    *result = ValInt{get_script_var_i(subject_p->is_player(), var_name, array_index)};
     return 0;
 }
 
@@ -1084,7 +1195,7 @@ int fun_read_script_str(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
     if (subject_p->bl_type != BL::PC)
         return 1;
 
-    RESULTSTR = dumb_string::copys(get_script_var_s(subject_p->is_player(), var_name, array_index));
+    *result = ValString{dumb_string::copys(get_script_var_s(subject_p->is_player(), var_name, array_index))};
     return 0;
 }
 
@@ -1094,12 +1205,13 @@ int fun_rbox(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
     location_t loc = ARGLOCATION(0);
     int radius = ARGINT(1);
 
-    RESULTAREA = area_new(AREA::RECT);
-    RESULTAREA->a.a_rect.loc.m = loc.m;
-    RESULTAREA->a.a_rect.loc.x = loc.x - radius;
-    RESULTAREA->a.a_rect.loc.y = loc.y - radius;
-    RESULTAREA->a.a_rect.width = radius * 2 + 1;
-    RESULTAREA->a.a_rect.height = radius * 2 + 1;
+    AreaRect a_rect;
+    a_rect.loc.m = loc.m;
+    a_rect.loc.x = loc.x - radius;
+    a_rect.loc.y = loc.y - radius;
+    a_rect.width = radius * 2 + 1;
+    a_rect.height = radius * 2 + 1;
+    *result = ValArea{dumb_ptr<area_t>::make(a_rect, a_rect.width * a_rect.height)};
 
     return 0;
 }
@@ -1111,28 +1223,28 @@ int fun_running_status_update(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
         return 1;
 
     StatusChange sc = static_cast<StatusChange>(ARGINT(1));
-    RESULTINT = bool(battle_get_sc_data(ARGENTITY(0))[sc].timer);
+    *result = ValInt{bool(battle_get_sc_data(ARGENTITY(0))[sc].timer)};
     return 0;
 }
 
 static
 int fun_status_option(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = (bool((ARGPC(0))->status.option & static_cast<Option>(ARGINT(1))));
+    *result = ValInt{(bool((ARGPC(0))->status.option & static_cast<Option>(ARGINT(1))))};
     return 0;
 }
 
 static
 int fun_element(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = static_cast<int>(battle_get_element(ARGENTITY(0)).element);
+    *result = ValInt{static_cast<int>(battle_get_element(ARGENTITY(0)).element)};
     return 0;
 }
 
 static
 int fun_element_level(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = battle_get_element(ARGENTITY(0)).level;
+    *result = ValInt{battle_get_element(ARGENTITY(0)).level};
     return 0;
 }
 
@@ -1140,14 +1252,14 @@ static
 int fun_is_exterior(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
 #warning "Evil assumptions!"
-    RESULTINT = ARGLOCATION(0).m->name_[4] == '1';
+    *result = ValInt{ARGLOCATION(0).m->name_[4] == '1'};
     return 0;
 }
 
 static
 int fun_contains_string(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = nullptr != strstr(ARGSTR(0).c_str(), ARGSTR(1).c_str());
+    *result = ValInt{nullptr != strstr(ARGSTR(0).c_str(), ARGSTR(1).c_str())};
     return 0;
 }
 
@@ -1155,14 +1267,14 @@ static
 int fun_strstr(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
     const char *offset = strstr(ARGSTR(0).c_str(), ARGSTR(1).c_str());
-    RESULTINT = offset - ARGSTR(0).c_str();
+    *result = ValInt{static_cast<int32_t>(offset - ARGSTR(0).c_str())};
     return offset == nullptr;
 }
 
 static
 int fun_strlen(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = strlen(ARGSTR(0).c_str());
+    *result = ValInt{static_cast<int32_t>(strlen(ARGSTR(0).c_str()))};
     return 0;
 }
 
@@ -1187,7 +1299,7 @@ int fun_substr(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 
     const char *begin = src + offset;
     const char *end = begin + len;
-    RESULTSTR = dumb_string::copy(begin, end);
+    *result = ValString{dumb_string::copy(begin, end)};
 
     return 0;
 }
@@ -1195,7 +1307,7 @@ int fun_substr(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 static
 int fun_sqrt(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
-    RESULTINT = static_cast<int>(sqrt(ARGINT(0)));
+    *result = ValInt{static_cast<int>(sqrt(ARGINT(0)))};
     return 0;
 }
 
@@ -1203,7 +1315,7 @@ static
 int fun_map_level(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 {
 #warning "Evil assumptions!"
-    RESULTINT = ARGLOCATION(0).m->name_[4] - '0';
+    *result = ValInt{ARGLOCATION(0).m->name_[4] - '0'};
     return 0;
 }
 
@@ -1213,8 +1325,8 @@ int fun_map_nr(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
 #warning "Evil assumptions!"
     MapName mapname = ARGLOCATION(0).m->name_;
 
-    RESULTINT = ((mapname[0] - '0') * 100)
-        + ((mapname[1] - '0') * 10) + ((mapname[2] - '0'));
+    *result = ValInt{((mapname[0] - '0') * 100)
+        + ((mapname[1] - '0') * 10) + ((mapname[2] - '0'))};
     return 0;
 }
 
@@ -1236,30 +1348,30 @@ int fun_dir_towards(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
         if (abs(dx) > abs(dy) * 2)
         {                       /* east or west */
             if (dx < 0)
-                RESULTINT = 2 /* west */ ;
+                *result = ValDir{DIR::W};
             else
-                RESULTINT = 6 /* east */ ;
+                *result = ValDir{DIR::E};
         }
         else if (abs(dy) > abs(dx) * 2)
         {                       /* north or south */
             if (dy > 0)
-                RESULTINT = 0 /* south */ ;
+                *result = ValDir{DIR::S};
             else
-                RESULTINT = 4 /* north */ ;
+                *result = ValDir{DIR::N};
         }
         else if (dx < 0)
         {                       /* north-west or south-west */
             if (dy < 0)
-                RESULTINT = 3 /* north-west */ ;
+                *result = ValDir{DIR::NW};
             else
-                RESULTINT = 1 /* south-west */ ;
+                *result = ValDir{DIR::SW};
         }
         else
         {                       /* north-east or south-east */
             if (dy < 0)
-                RESULTINT = 5 /* north-east */ ;
+                *result = ValDir{DIR::NE};
             else
-                RESULTINT = 7 /* south-east */ ;
+                *result = ValDir{DIR::SE};
         }
     }
     else
@@ -1268,16 +1380,16 @@ int fun_dir_towards(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
         if (abs(dx) > abs(dy))
         {                       /* east or west */
             if (dx < 0)
-                RESULTINT = 2 /* west */ ;
+                *result = ValDir{DIR::W};
             else
-                RESULTINT = 6 /* east */ ;
+                *result = ValDir{DIR::E};
         }
         else
         {                       /* north or south */
             if (dy > 0)
-                RESULTINT = 0 /* south */ ;
+                *result = ValDir{DIR::S};
             else
-                RESULTINT = 4 /* north */ ;
+                *result = ValDir{DIR::N};
         }
     }
 
@@ -1290,9 +1402,9 @@ int fun_extract_healer_xp(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
     dumb_ptr<map_session_data> sd = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : nullptr;
 
     if (!sd)
-        RESULTINT = 0;
+        *result = ValInt{0};
     else
-        RESULTINT = pc_extract_healer_exp(sd, ARGINT(1));
+        *result = ValInt{pc_extract_healer_exp(sd, ARGINT(1))};
     return 0;
 }
 
@@ -1391,24 +1503,24 @@ fun_t *magic_get_fun(ZString name)
 
 // 1 on failure
 static
-int eval_location(dumb_ptr<env_t> env, location_t *dest, e_location_t *expr)
+int eval_location(dumb_ptr<env_t> env, location_t *dest, const e_location_t *expr)
 {
     val_t m, x, y;
     magic_eval(env, &m, expr->m);
     magic_eval(env, &x, expr->x);
     magic_eval(env, &y, expr->y);
 
-    if (CHECK_TYPE(&m, TYPE::STRING)
-        && CHECK_TYPE(&x, TYPE::INT) && CHECK_TYPE(&y, TYPE::INT))
+    if (m.is<ValString>()
+        && x.is<ValInt>() && y.is<ValInt>())
     {
-        MapName name = VString<15>(ZString(m.v.v_string));
+        MapName name = VString<15>(ZString(m.get_if<ValString>()->v_string));
         map_local *map_id = map_mapname2mapid(name);
         magic_clear_var(&m);
         if (!map_id)
             return 1;
         dest->m = map_id;
-        dest->x = x.v.v_int;
-        dest->y = y.v.v_int;
+        dest->x = x.get_if<ValInt>()->v_int;
+        dest->y = y.get_if<ValInt>()->v_int;
         return 0;
     }
     else
@@ -1421,141 +1533,145 @@ int eval_location(dumb_ptr<env_t> env, location_t *dest, e_location_t *expr)
 }
 
 static
-dumb_ptr<area_t> eval_area(dumb_ptr<env_t> env, e_area_t& expr_)
+dumb_ptr<area_t> eval_area(dumb_ptr<env_t> env, const e_area_t& expr_)
 {
-    e_area_t *expr = &expr_; // temporary hack to reduce diff
-    auto area = dumb_ptr<area_t>::make();
-    area->ty = expr->ty;
-
-    switch (expr->ty)
+    MATCH (expr_)
     {
-        case AREA::LOCATION:
-            area->size = 1;
-            if (eval_location(env, &area->a.a_loc, &expr->a.a_loc))
+        CASE (const e_location_t&, a_loc)
+        {
+            location_t loc;
+            int size = 1;
+            if (eval_location(env, &loc, &a_loc))
             {
-                area.delete_();
                 return nullptr;
             }
             else
-                return area;
-
-        case AREA::UNION:
+            {
+                return dumb_ptr<area_t>::make(loc, size);
+            }
+        }
+        CASE (const ExprAreaUnion&, a)
         {
-            int i, fail = 0;
-            for (i = 0; i < 2; i++)
+            AreaUnion u;
+            bool fail = false;
+            for (int i = 0; i < 2; i++)
             {
-                area->a.a_union[i] = eval_area(env, *expr->a.a_union[i]);
-                if (!area->a.a_union[i])
-                    fail = 1;
+                u.a_union[i] = eval_area(env, *a.a_union[i]);
+                if (!u.a_union[i])
+                    fail = true;
             }
 
             if (fail)
             {
-                for (i = 0; i < 2; i++)
+                for (int i = 0; i < 2; i++)
                 {
-                    if (area->a.a_union[i])
-                        free_area(area->a.a_union[i]);
+                    if (u.a_union[i])
+                        free_area(u.a_union[i]);
                 }
-                area.delete_();
                 return nullptr;
             }
-            area->size = area->a.a_union[0]->size + area->a.a_union[1]->size;
-            return area;
+            int size = u.a_union[0]->size + u.a_union[1]->size;
+            return dumb_ptr<area_t>::make(u, size);
         }
-
-        case AREA::RECT:
+        CASE (const ExprAreaRect&, a_rect)
         {
             val_t width, height;
-            magic_eval(env, &width, expr->a.a_rect.width);
-            magic_eval(env, &height, expr->a.a_rect.height);
-
-            area->a.a_rect.width = width.v.v_int;
-            area->a.a_rect.height = height.v.v_int;
-
-            if (CHECK_TYPE(&width, TYPE::INT)
-                && CHECK_TYPE(&height, TYPE::INT)
-                && !eval_location(env, &(area->a.a_rect.loc),
-                                   &expr->a.a_rect.loc))
+            magic_eval(env, &width, a_rect.width);
+            magic_eval(env, &height, a_rect.height);
+
+            AreaRect a_rect_;
+            if (width.is<ValInt>()
+                && height.is<ValInt>()
+                && !eval_location(env, &(a_rect_.loc),
+                                   &a_rect.loc))
             {
-                area->size = area->a.a_rect.width * area->a.a_rect.height;
+                a_rect_.width = width.get_if<ValInt>()->v_int;
+                a_rect_.height = height.get_if<ValInt>()->v_int;
+
+                int size = a_rect_.width * a_rect_.height;
                 magic_clear_var(&width);
                 magic_clear_var(&height);
-                return area;
+                return dumb_ptr<area_t>::make(a_rect_, size);
             }
             else
             {
-                area.delete_();
                 magic_clear_var(&width);
                 magic_clear_var(&height);
                 return nullptr;
             }
         }
-
-        case AREA::BAR:
+        CASE (const ExprAreaBar&, a_bar)
         {
             val_t width, depth, dir;
-            magic_eval(env, &width, expr->a.a_bar.width);
-            magic_eval(env, &depth, expr->a.a_bar.depth);
-            magic_eval(env, &dir, expr->a.a_bar.dir);
-
-            area->a.a_bar.width = width.v.v_int;
-            area->a.a_bar.depth = depth.v.v_int;
-            area->a.a_bar.dir = dir.v.v_dir;
-
-            if (CHECK_TYPE(&width, TYPE::INT)
-                && CHECK_TYPE(&depth, TYPE::INT)
-                && CHECK_TYPE(&dir, TYPE::DIR)
-                && !eval_location(env, &area->a.a_bar.loc,
-                                   &expr->a.a_bar.loc))
+            magic_eval(env, &width, a_bar.width);
+            magic_eval(env, &depth, a_bar.depth);
+            magic_eval(env, &dir, a_bar.dir);
+
+            AreaBar a_bar_;
+            if (width.is<ValInt>()
+                && depth.is<ValInt>()
+                && dir.is<ValDir>()
+                && !eval_location(env, &a_bar_.loc,
+                                   &a_bar.loc))
             {
-                area->size =
-                    (area->a.a_bar.width * 2 + 1) * area->a.a_bar.depth;
+                a_bar_.width = width.get_if<ValInt>()->v_int;
+                a_bar_.depth = depth.get_if<ValInt>()->v_int;
+                a_bar_.dir = dir.get_if<ValDir>()->v_dir;
+
+                int size = (a_bar_.width * 2 + 1) * a_bar_.depth;
                 magic_clear_var(&width);
                 magic_clear_var(&depth);
                 magic_clear_var(&dir);
-                return area;
+                return dumb_ptr<area_t>::make(a_bar_, size);
             }
             else
             {
-                area.delete_();
                 magic_clear_var(&width);
                 magic_clear_var(&depth);
                 magic_clear_var(&dir);
                 return nullptr;
             }
         }
-
-        default:
-            FPRINTF(stderr, "INTERNAL ERROR: Unknown area type %d\n"_fmt,
-                    area->ty);
-            area.delete_();
-            return nullptr;
     }
+    abort();
 }
 
+// This is called on arguments with begin=true,
+// and on the return value with begin=false.
+// In both cases, the ambiguous types are in pointer mode.
 static
-TYPE type_key(char ty_key)
+bool type_key_matches(char ty_key, val_t *arg, bool begin)
 {
     switch (ty_key)
     {
         case 'i':
-            return TYPE::INT;
+            if (begin)
+                intify(arg);
+            return arg->is<ValInt>();
         case 'd':
-            return TYPE::DIR;
+            return arg->is<ValDir>();
         case 's':
-            return TYPE::STRING;
+            if (begin)
+                stringify(arg);
+            return arg->is<ValString>();
         case 'e':
-            return TYPE::ENTITY;
+            return arg->is<ValEntityPtr>();
         case 'l':
-            return TYPE::LOCATION;
+            if (begin)
+                make_location(arg);
+            return arg->is<ValLocation>();
         case 'a':
-            return TYPE::AREA;
+            if (begin)
+                make_area(arg);
+            return arg->is<ValArea>();
         case 'S':
-            return TYPE::SPELL;
+            if (begin)
+                make_spell(arg);
+            return arg->is<ValSpell>();
         case 'I':
-            return TYPE::INVOCATION;
+            return arg->is<ValInvocationPtr>();
         default:
-            return TYPE::NEGATIVE_1;
+            return true;
     }
 }
 
@@ -1566,24 +1682,35 @@ int magic_signature_check(ZString opname, ZString funname, ZString signature,
     for (i = 0; i < args.size(); i++)
     {
         val_t *arg = &args[i];
-        char ty_key = signature[i];
-        TYPE ty = arg->ty;
-        TYPE desired_ty = type_key(ty_key);
 
-        if (ty == TYPE::ENTITY)
+        // whoa, it turns out the second p *does* shadow this one
+        if (ValEntityInt *p1 = arg->get_if<ValEntityInt>())
         {
             /* Dereference entities in preparation for calling function */
-            arg->v.v_entity = map_id2bl(wrap<BlockId>(static_cast<uint32_t>(arg->v.v_int)));
-            if (!arg->v.v_entity)
-                ty = arg->ty = TYPE::FAIL;
+            dumb_ptr<block_list> ent = map_id2bl(p1->v_eid);
+            if (ent)
+            {
+                *arg = ValEntityPtr{ent};
+            }
+            else
+            {
+                *arg = ValFail{};
+            }
         }
-        else if (ty == TYPE::INVOCATION)
+        else if (ValInvocationInt *p2 = arg->get_if<ValInvocationInt>())
         {
-            arg->v.v_invocation = map_id2bl(wrap<BlockId>(static_cast<uint32_t>(arg->v.v_int)))->is_spell();
-            if (!arg->v.v_entity)
-                ty = arg->ty = TYPE::FAIL;
+            dumb_ptr<invocation> invoc = map_id2bl(p2->v_iid)->is_spell();
+            if (invoc)
+            {
+                *arg = ValInvocationPtr{invoc};
+            }
+            else
+            {
+                *arg = ValFail();
+            }
         }
 
+        char ty_key = signature[i];
         if (!ty_key)
         {
             FPRINTF(stderr,
@@ -1592,13 +1719,14 @@ int magic_signature_check(ZString opname, ZString funname, ZString signature,
             return 1;
         }
 
-        if (ty == TYPE::FAIL && ty_key != '_')
+        if (arg->is<ValFail>() && ty_key != '_')
             return 1;           /* Fail `in a sane way':  This is a perfectly permissible error */
 
-        if (ty == desired_ty || desired_ty == TYPE::NEGATIVE_1)
+        // this also does conversions now
+        if (type_key_matches(ty_key, arg, true))
             continue;
 
-        if (ty == TYPE::UNDEF)
+        if (arg->is<ValUndef>())
         {
             FPRINTF(stderr,
                     "[magic-eval]:  L%d:%d: Argument #%d to %s `%s' undefined\n"_fmt,
@@ -1606,36 +1734,14 @@ int magic_signature_check(ZString opname, ZString funname, ZString signature,
             return 1;
         }
 
-        /* If we are here, we have a type mismatch but no failure _yet_.  Try to coerce. */
-        switch (desired_ty)
-        {
-            case TYPE::INT:
-                intify(arg);
-                break;          /* 100% success rate */
-            case TYPE::STRING:
-                stringify(arg, 1);
-                break;          /* 100% success rate */
-            case TYPE::AREA:
-                make_area(arg);
-                break;          /* Only works for locations */
-            case TYPE::LOCATION:
-                make_location(arg);
-                break;          /* Only works for some areas */
-            case TYPE::SPELL:
-                make_spell(arg);
-                break;          /* Only works for still-active invocatoins */
-            default:
-                break;          /* We'll fail right below */
-        }
 
-        ty = arg->ty;
-        if (ty != desired_ty)
         {                       /* Coercion failed? */
-            if (ty != TYPE::FAIL)
+            if (!arg->is<ValFail>())
+            {
                 FPRINTF(stderr,
-                        "[magic-eval]:  L%d:%d: Argument #%d to %s `%s' of incorrect type (%d)\n"_fmt,
-                        line, column, i + 1, opname, funname,
-                        ty);
+                        "[magic-eval]:  L%d:%d: Argument #%d to %s `%s' of incorrect type (sorry, types aren't integers anymore)\n"_fmt,
+                        line, column, i + 1, opname, funname);
+            }
             return 1;
         }
     }
@@ -1645,82 +1751,83 @@ int magic_signature_check(ZString opname, ZString funname, ZString signature,
 
 void magic_eval(dumb_ptr<env_t> env, val_t *dest, dumb_ptr<expr_t> expr)
 {
-    switch (expr->ty)
+    MATCH (*expr)
     {
-        case EXPR::VAL:
-            magic_copy_var(dest, &expr->e.e_val);
-            break;
+        CASE (const val_t&, e_val)
+        {
+            magic_copy_var(dest, &e_val);
+        }
 
-        case EXPR::LOCATION:
-            if (eval_location(env, &dest->v.v_location, &expr->e.e_location))
-                dest->ty = TYPE::FAIL;
+        CASE (const e_location_t&, e_location)
+        {
+            location_t loc;
+            if (eval_location(env, &loc, &e_location))
+                *dest = ValFail();
             else
-                dest->ty = TYPE::LOCATION;
-            break;
-
-        case EXPR::AREA:
-            if ((dest->v.v_area = eval_area(env, expr->e.e_area)))
-                dest->ty = TYPE::AREA;
+                *dest = ValLocation{loc};
+        }
+        CASE (const e_area_t&, e_area)
+        {
+            if (dumb_ptr<area_t> area = eval_area(env, e_area))
+                *dest = ValArea{area};
             else
-                dest->ty = TYPE::FAIL;
-            break;
-
-        case EXPR::FUNAPP:
+                *dest = ValFail();
+        }
+        CASE (const ExprFunApp&, e_funapp)
         {
             val_t arguments[MAX_ARGS];
-            int args_nr = expr->e.e_funapp.args_nr;
+            int args_nr = e_funapp.args_nr;
             int i;
-            fun_t *f = expr->e.e_funapp.funp;
+            fun_t *f = e_funapp.funp;
 
             for (i = 0; i < args_nr; ++i)
-                magic_eval(env, &arguments[i], expr->e.e_funapp.args[i]);
+                magic_eval(env, &arguments[i], e_funapp.args[i]);
             if (magic_signature_check("function"_s, f->name, f->signature, Slice<val_t>(arguments, args_nr),
-                        expr->e.e_funapp.line_nr, expr->e.e_funapp.column)
+                        e_funapp.line_nr, e_funapp.column)
                     || f->fun(env, dest, Slice<val_t>(arguments, args_nr)))
-                dest->ty = TYPE::FAIL;
+                *dest = ValFail();
             else
             {
-                TYPE dest_ty = type_key(f->ret_ty);
-                if (dest_ty != TYPE::NEGATIVE_1)
-                    dest->ty = dest_ty;
+                assert (!dest->is<ValInvocationPtr>());
+                assert (!dest->is<ValInvocationInt>());
+                assert (!dest->is<ValEntityInt>());
+                assert (type_key_matches(f->ret_ty, dest, false));
 
                 /* translate entity back into persistent int */
-                if (dest->ty == TYPE::ENTITY)
+                if (ValEntityPtr *ent = dest->get_if<ValEntityPtr>())
                 {
-                    if (dest->v.v_entity)
-                        dest->v.v_int = static_cast<int32_t>(unwrap<BlockId>(dest->v.v_entity->bl_id));
+                    if (ent->v_entity)
+                        *dest = ValEntityInt{ent->v_entity->bl_id};
                     else
-                        dest->ty = TYPE::FAIL;
+                        *dest = ValFail();
                 }
+                // what about invocation?
             }
 
             for (i = 0; i < args_nr; ++i)
                 magic_clear_var(&arguments[i]);
-            break;
         }
-
-        case EXPR::ID:
+        CASE (const ExprId&, e)
         {
-            val_t v = env->VAR(expr->e.e_id);
+            val_t& v = env->VAR(e.e_id);
             magic_copy_var(dest, &v);
-            break;
         }
-
-        case EXPR::SPELLFIELD:
+        CASE (const ExprField&, e_field)
         {
             val_t v;
-            int id = expr->e.e_field.id;
-            magic_eval(env, &v, expr->e.e_field.expr);
+            int id = e_field.id;
+            magic_eval(env, &v, e_field.expr);
 
-            if (v.ty == TYPE::INVOCATION)
+            assert(!v.is<ValInvocationPtr>());
+            if (ValInvocationInt *ii = v.get_if<ValInvocationInt>())
             {
-                dumb_ptr<invocation> t = map_id2bl(wrap<BlockId>(static_cast<uint32_t>(v.v.v_int)))->is_spell();
+                dumb_ptr<invocation> t = map_id2bl(ii->v_iid)->is_spell();
 
                 if (!t)
-                    dest->ty = TYPE::UNDEF;
+                    *dest = ValUndef();
                 else
                 {
-                    val_t val = t->env->VAR(id);
+                    val_t& val = t->env->VAR(id);
                     magic_copy_var(dest, &val);
                 }
             }
@@ -1729,16 +1836,9 @@ void magic_eval(dumb_ptr<env_t> env, val_t *dest, dumb_ptr<expr_t> expr)
                 FPRINTF(stderr,
                         "[magic] Attempt to access field %s on non-spell\n"_fmt,
                         env->base_env->varv[id].name);
-                dest->ty = TYPE::FAIL;
+                *dest = ValFail();
             }
-            break;
         }
-
-        default:
-            FPRINTF(stderr,
-                    "[magic] INTERNAL ERROR: Unknown expression type %d\n"_fmt,
-                    expr->ty);
-            break;
     }
 }
 
@@ -1747,12 +1847,12 @@ int magic_eval_int(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr)
     val_t result;
     magic_eval(env, &result, expr);
 
-    if (result.ty == TYPE::FAIL || result.ty == TYPE::UNDEF)
+    if (result.is<ValFail>() || result.is<ValUndef>())
         return 0;
 
     intify(&result);
 
-    return result.v.v_int;
+    return result.get_if<ValInt>()->v_int;
 }
 
 AString magic_eval_str(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr)
@@ -1760,19 +1860,13 @@ AString magic_eval_str(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr)
     val_t result;
     magic_eval(env, &result, expr);
 
-    if (result.ty == TYPE::FAIL || result.ty == TYPE::UNDEF)
+    if (result.is<ValFail>() || result.is<ValUndef>())
         return "?"_s;
 
-    stringify(&result, 0);
+    // is this a memory leak?
+    stringify(&result);
 
-    return result.v.v_string.str();
-}
-
-dumb_ptr<expr_t> magic_new_expr(EXPR ty)
-{
-    auto expr = dumb_ptr<expr_t>::make();
-    expr->ty = ty;
-    return expr;
+    return result.get_if<ValString>()->v_string.str();
 }
 } // namespace magic
 } // namespace tmwa
diff --git a/src/map/magic-expr.hpp b/src/map/magic-expr.hpp
index 4c1efe2..294e665 100644
--- a/src/map/magic-expr.hpp
+++ b/src/map/magic-expr.hpp
@@ -80,11 +80,9 @@ int magic_eval_int(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr);
  */
 AString magic_eval_str(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr);
 
-dumb_ptr<expr_t> magic_new_expr(EXPR ty);
-
 void magic_clear_var(val_t *v);
 
-void magic_copy_var(val_t *dest, val_t *src);
+void magic_copy_var(val_t *dest, const val_t *src);
 
 void magic_random_location(location_t *dest, dumb_ptr<area_t> area);
 
diff --git a/src/map/magic-interpreter-base.cpp b/src/map/magic-interpreter-base.cpp
index cc09ed9..1ac391a 100644
--- a/src/map/magic-interpreter-base.cpp
+++ b/src/map/magic-interpreter-base.cpp
@@ -42,53 +42,41 @@ namespace tmwa
 namespace magic
 {
 static
-void set_int_p(val_t *v, int i, TYPE t)
+void set_int(val_t *v, int i)
 {
-    v->ty = t;
-    v->v.v_int = i;
+    *v = ValInt{i};
 }
 
-#warning "This code should die"
-DIAG_PUSH();
-DIAG_I(unused_macros);
-
-#define set_int(v, i) set_int_p(v, i, TYPE::INT)
-#define set_dir(v, i) set_int_p(v, i, TYPE::DIR)
+static __attribute__((unused))
+void set_dir(val_t *v, DIR d)
+{
+    *v = ValDir{d};
+}
 
-#define SETTER(tty, dyn_ty, field) (val_t *v, tty x) { v->ty = dyn_ty; v->v.field = x; }
 
 static
-void set_string SETTER(dumb_string, TYPE::STRING, v_string)
+void set_string(val_t *v, dumb_string x)
+{
+    *v = ValString{x};
+}
 
 static
 void set_entity(val_t *v, dumb_ptr<block_list> e)
 {
-    v->ty = TYPE::ENTITY;
-    v->v.v_int = static_cast<int32_t>(unwrap<BlockId>(e->bl_id));
+    *v = ValEntityInt{e->bl_id};
 }
 
 static
 void set_invocation(val_t *v, dumb_ptr<invocation> i)
 {
-    v->ty = TYPE::INVOCATION;
-    v->v.v_int = static_cast<int32_t>(unwrap<BlockId>(i->bl_id));
+    *v = ValInvocationInt{i->bl_id};
 }
 
 static
-void set_spell SETTER(dumb_ptr<spell_t>, TYPE::SPELL, v_spell)
-
-#define setenv(f, v, x) f(&(env->varu[v]), x)
-
-#define set_env_int(v, x) setenv(set_int, v, x)
-#define set_env_dir(v, x) setenv(set_dir, v, x)
-#define set_env_string(v, x) setenv(set_string, v, x)
-#define set_env_entity(v, x) setenv(set_entity, v, x)
-#define set_env_location(v, x) setenv(set_location, v, x)
-#define set_env_area(v, x) setenv(set_area, v, x)
-#define set_env_invocation(v, x) setenv(set_invocation, v, x)
-#define set_env_spell(v, x) setenv(set_spell, v, x)
-
-DIAG_POP();
+void set_spell(val_t *v, dumb_ptr<spell_t> x)
+{
+    *v = ValSpell{x};
+}
 
 magic_conf_t magic_conf;        /* Global magic conf */
 env_t magic_default_env = { &magic_conf, nullptr };
@@ -176,7 +164,7 @@ dumb_ptr<env_t> spell_create_env(magic_conf_t *conf, dumb_ptr<spell_t> spell,
     {
 
         case SPELLARG::STRING:
-            set_env_string(spell->arg, dumb_string::copys(param));
+            set_string(&env->varu[spell->arg], dumb_string::copys(param));
             break;
 
         case SPELLARG::PC:
@@ -185,7 +173,7 @@ dumb_ptr<env_t> spell_create_env(magic_conf_t *conf, dumb_ptr<spell_t> spell,
             dumb_ptr<map_session_data> subject = map_nick2sd(name);
             if (!subject)
                 subject = caster;
-            set_env_entity(spell->arg, subject);
+            set_entity(&env->varu[spell->arg], subject);
             break;
         }
 
@@ -197,9 +185,9 @@ dumb_ptr<env_t> spell_create_env(magic_conf_t *conf, dumb_ptr<spell_t> spell,
                     spell->spellarg_ty);
     }
 
-    set_env_entity(VAR_CASTER, caster);
-    set_env_int(VAR_SPELLPOWER, spellpower);
-    set_env_spell(VAR_SPELL, spell);
+    set_entity(&env->varu[VAR_CASTER], caster);
+    set_int(&env->varu[VAR_SPELLPOWER], spellpower);
+    set_spell(&env->varu[VAR_SPELL], spell);
 
     return env;
 }
@@ -301,8 +289,10 @@ int spellguard_can_satisfy(spellguard_check_t *check, dumb_ptr<map_session_data>
     {
         interval_t casttime = check->casttime;
 
-        if (env->VAR(VAR_MIN_CASTTIME).ty == TYPE::INT)
-            casttime = std::max(casttime, static_cast<interval_t>(env->VAR(VAR_MIN_CASTTIME).v.v_int));
+        if (ValInt *v = env->VAR(VAR_MIN_CASTTIME).get_if<ValInt>())
+        {
+            casttime = std::max(casttime, static_cast<interval_t>(v->v_int));
+        }
 
         caster->cast_tick = tick + casttime;    /* Make sure not to cast too frequently */
 
@@ -314,7 +304,7 @@ int spellguard_can_satisfy(spellguard_check_t *check, dumb_ptr<map_session_data>
 }
 
 static
-effect_set_t *spellguard_check_sub(spellguard_check_t *check,
+const effect_set_t *spellguard_check_sub(spellguard_check_t *check,
         dumb_ptr<spellguard_t> guard,
         dumb_ptr<map_session_data> caster,
         dumb_ptr<env_t> env,
@@ -323,25 +313,25 @@ effect_set_t *spellguard_check_sub(spellguard_check_t *check,
     if (guard == nullptr)
         return nullptr;
 
-    switch (guard->ty)
+    MATCH (*guard)
     {
-        case SPELLGUARD::CONDITION:
-            if (!magic_eval_int(env, guard->s.s_condition))
+        CASE (const GuardCondition&, s)
+        {
+            if (!magic_eval_int(env, s.s_condition))
                 return nullptr;
-            break;
-
-        case SPELLGUARD::COMPONENTS:
-            copy_components(&check->components, guard->s.s_components);
-            break;
-
-        case SPELLGUARD::CATALYSTS:
-            copy_components(&check->catalysts, guard->s.s_catalysts);
-            break;
-
-        case SPELLGUARD::CHOICE:
+        }
+        CASE (const GuardComponents&, s)
+        {
+            copy_components(&check->components, s.s_components);
+        }
+        CASE (const GuardCatalysts&, s)
+        {
+            copy_components(&check->catalysts, s.s_catalysts);
+        }
+        CASE (const GuardChoice&, s)
         {
             spellguard_check_t altcheck = *check;
-            effect_set_t *retval;
+            const effect_set_t *retval;
 
             altcheck.components = nullptr;
             altcheck.catalysts = nullptr;
@@ -357,40 +347,36 @@ effect_set_t *spellguard_check_sub(spellguard_check_t *check,
             if (retval)
                 return retval;
             else
-                return spellguard_check_sub(check, guard->s.s_alt, caster,
+                return spellguard_check_sub(check, s.s_alt, caster,
                                              env, near_miss);
         }
-
-        case SPELLGUARD::MANA:
-            check->mana += magic_eval_int(env, guard->s.s_mana);
-            break;
-
-        case SPELLGUARD::CASTTIME:
-            check->casttime += static_cast<interval_t>(magic_eval_int(env, guard->s.s_mana));
-            break;
-
-        case SPELLGUARD::EFFECT:
+        CASE (const GuardMana&, s)
+        {
+            check->mana += magic_eval_int(env, s.s_mana);
+        }
+        CASE (const GuardCastTime&, s)
+        {
+            check->casttime += static_cast<interval_t>(magic_eval_int(env, s.s_casttime));
+        }
+        CASE (const effect_set_t&, s_effect)
+        {
             if (spellguard_can_satisfy(check, caster, env, near_miss))
-                return &guard->s.s_effect;
+                return &s_effect;
             else
                 return nullptr;
-
-        default:
-            FPRINTF(stderr, "Unexpected spellguard type %d\n"_fmt,
-                    guard->ty);
-            return nullptr;
+        }
     }
 
     return spellguard_check_sub(check, guard->next, caster, env, near_miss);
 }
 
 static
-effect_set_t *check_spellguard(dumb_ptr<spellguard_t> guard,
+const effect_set_t *check_spellguard(dumb_ptr<spellguard_t> guard,
         dumb_ptr<map_session_data> caster, dumb_ptr<env_t> env,
         int *near_miss)
 {
     spellguard_check_t check;
-    effect_set_t *retval;
+    const effect_set_t *retval;
     check.catalysts = nullptr;
     check.components = nullptr;
     check.mana = 0;
@@ -408,7 +394,7 @@ effect_set_t *check_spellguard(dumb_ptr<spellguard_t> guard,
 /* Public API */
 /* -------------------------------------------------------------------------------- */
 
-effect_set_t *spell_trigger(dumb_ptr<spell_t> spell, dumb_ptr<map_session_data> caster,
+const effect_set_t *spell_trigger(dumb_ptr<spell_t> spell, dumb_ptr<map_session_data> caster,
         dumb_ptr<env_t> env, int *near_miss)
 {
     dumb_ptr<spellguard_t> guard = spell->spellguard;
@@ -426,10 +412,11 @@ static
 void spell_set_location(dumb_ptr<invocation> invocation, dumb_ptr<block_list> entity)
 {
     magic_clear_var(&invocation->env->varu[VAR_LOCATION]);
-    invocation->env->varu[VAR_LOCATION].ty = TYPE::LOCATION;
-    invocation->env->varu[VAR_LOCATION].v.v_location.m = entity->bl_m;
-    invocation->env->varu[VAR_LOCATION].v.v_location.x = entity->bl_x;
-    invocation->env->varu[VAR_LOCATION].v.v_location.y = entity->bl_y;
+    ValLocation v;
+    v.v_location.m = entity->bl_m;
+    v.v_location.x = entity->bl_x;
+    v.v_location.y = entity->bl_y;
+    invocation->env->varu[VAR_LOCATION] = v;
 }
 
 void spell_update_location(dumb_ptr<invocation> invocation)
@@ -447,7 +434,7 @@ void spell_update_location(dumb_ptr<invocation> invocation)
     }
 }
 
-dumb_ptr<invocation> spell_instantiate(effect_set_t *effect_set, dumb_ptr<env_t> env)
+dumb_ptr<invocation> spell_instantiate(const effect_set_t *effect_set, dumb_ptr<env_t> env)
 {
     dumb_ptr<invocation> retval;
     retval.new_();
@@ -455,9 +442,8 @@ dumb_ptr<invocation> spell_instantiate(effect_set_t *effect_set, dumb_ptr<env_t>
 
     retval->env = env;
 
-    retval->caster = wrap<BlockId>(static_cast<uint32_t>(env->VAR(VAR_CASTER).v.v_int));
-    retval->spell = env->VAR(VAR_SPELL).v.v_spell;
-    retval->stack_size = 0;
+    retval->caster = env->VAR(VAR_CASTER).get_if<ValEntityInt>()->v_eid;
+    retval->spell = env->VAR(VAR_SPELL).get_if<ValSpell>()->v_spell;
     retval->current_effect = effect_set->effect;
     retval->trigger_effect = effect_set->at_trigger;
     retval->end_effect = effect_set->at_end;
@@ -470,7 +456,7 @@ dumb_ptr<invocation> spell_instantiate(effect_set_t *effect_set, dumb_ptr<env_t>
     retval->bl_y = caster->bl_y;
 
     map_addblock(retval);
-    set_env_invocation(VAR_INVOCATION, retval);
+    set_invocation(&env->varu[VAR_INVOCATION], retval);
 
     return retval;
 }
@@ -491,7 +477,6 @@ dumb_ptr<invocation> spell_clone_effect(dumb_ptr<invocation> base)
     retval->caster = base->caster;
     retval->subject = BlockId();
     // retval->timer = 0;
-    retval->stack_size = 0;
     // retval->stack = undef;
     retval->script_pos = 0;
     // huh?
@@ -509,7 +494,7 @@ dumb_ptr<invocation> spell_clone_effect(dumb_ptr<invocation> base)
     retval->bl_type = base->bl_type;
 
     retval->bl_id = map_addobject(retval);
-    set_env_invocation(VAR_INVOCATION, retval);
+    set_invocation(&env->varu[VAR_INVOCATION], retval);
 
     return retval;
 }
diff --git a/src/map/magic-interpreter-base.hpp b/src/map/magic-interpreter-base.hpp
index 8c05df0..4bb41a0 100644
--- a/src/map/magic-interpreter-base.hpp
+++ b/src/map/magic-interpreter-base.hpp
@@ -64,11 +64,11 @@ void magic_free_env(dumb_ptr<env_t> env);
 /**
  * near_miss is set to nonzero iff the spell only failed due to ephemereal issues (spell delay in effect, out of mana, out of components)
  */
-effect_set_t *spell_trigger(dumb_ptr<spell_t> spell,
+const effect_set_t *spell_trigger(dumb_ptr<spell_t> spell,
         dumb_ptr<map_session_data> caster,
         dumb_ptr<env_t> env, int *near_miss);
 
-dumb_ptr<invocation> spell_instantiate(effect_set_t *effect, dumb_ptr<env_t> env);
+dumb_ptr<invocation> spell_instantiate(const effect_set_t *effect, dumb_ptr<env_t> env);
 
 /**
  * Bind a spell to a subject (this is a no-op for `local' spells).
diff --git a/src/map/magic-interpreter.hpp b/src/map/magic-interpreter.hpp
index 62ab504..01775b3 100644
--- a/src/map/magic-interpreter.hpp
+++ b/src/map/magic-interpreter.hpp
@@ -32,6 +32,8 @@
 
 #include "../generic/fwd.hpp"
 
+#include "../sexpr/variant.hpp"
+
 #include "../net/timer.t.hpp"
 
 #include "../mmo/ids.hpp"
@@ -52,55 +54,133 @@ struct location_t
     int x, y;
 };
 
-struct area_t
+struct AreaUnion
+{
+    dumb_ptr<area_t> a_union[2];
+};
+struct AreaRect
+{
+    location_t loc;
+    int width, height;
+};
+struct AreaBar
+{
+    location_t loc;
+    int width, depth;
+    DIR dir;
+};
+
+using AreaVariantBase = Variant<
+    location_t,
+    AreaUnion,
+    AreaRect,
+    AreaBar
+>;
+
+struct area_t : AreaVariantBase
 {
-    union au
-    {
-        location_t a_loc;
-        struct
-        {
-            location_t loc;
-            int width, depth;
-            DIR dir;
-        } a_bar;
-        struct
-        {
-            location_t loc;
-            int width, height;
-        } a_rect;
-        dumb_ptr<area_t> a_union[2];
-
-        au() { really_memzero_this(this); }
-        ~au() = default;
-        au(const au&) = default;
-        au& operator = (const au&) = default;
-    } a;
     int size;
-    AREA ty;
+
+    area_t() = delete;
+    area_t(area_t&&) = default;
+    area_t(const area_t&) = delete;
+    area_t& operator = (area_t&&) = default;
+    area_t& operator = (const area_t&) = delete;
+
+    area_t(location_t v, int sz) : AreaVariantBase(std::move(v)), size(sz) {}
+    area_t(AreaUnion v, int sz) : AreaVariantBase(std::move(v)), size(sz) {}
+    area_t(AreaRect v, int sz) : AreaVariantBase(std::move(v)), size(sz) {}
+    area_t(AreaBar v, int sz) : AreaVariantBase(std::move(v)), size(sz) {}
 };
 
-struct val_t
+struct ValUndef
 {
-    union vu
-    {
-        int v_int;
-        DIR v_dir;
-        dumb_string v_string;
-        /* Used ONLY during operation/function invocation; otherwise we use v_int */
-        dumb_ptr<block_list> v_entity;
-        dumb_ptr<area_t> v_area;
-        location_t v_location;
-        /* Used ONLY during operation/function invocation; otherwise we use v_int */
-        dumb_ptr<invocation> v_invocation;
-        dumb_ptr<spell_t> v_spell;
-
-        vu() { really_memzero_this(this); }
-        ~vu() = default;
-        vu(const vu&) = default;
-        vu& operator = (const vu&) = default;
-    } v;
-    TYPE ty;
 };
+struct ValInt
+{
+    int v_int;
+};
+struct ValDir
+{
+    DIR v_dir;
+};
+struct ValString
+{
+    dumb_string v_string;
+};
+struct ValEntityInt
+{
+    BlockId v_eid;
+};
+struct ValEntityPtr
+{
+    dumb_ptr<block_list> v_entity;
+};
+struct ValLocation
+{
+    location_t v_location;
+};
+struct ValArea
+{
+    dumb_ptr<area_t> v_area;
+};
+struct ValSpell
+{
+    dumb_ptr<spell_t> v_spell;
+};
+struct ValInvocationInt
+{
+    BlockId v_iid;
+};
+struct ValInvocationPtr
+{
+    dumb_ptr<invocation> v_invocation;
+};
+struct ValFail
+{
+};
+struct ValNegative1
+{
+};
+
+using ValVariantBase = Variant<
+    ValUndef,
+    ValInt,
+    ValDir,
+    ValString,
+    ValEntityInt,
+    ValEntityPtr,
+    ValLocation,
+    ValArea,
+    ValSpell,
+    ValInvocationInt,
+    ValInvocationPtr,
+    ValFail,
+    ValNegative1
+>;
+struct val_t : ValVariantBase
+{
+    val_t() noexcept : ValVariantBase(ValUndef{}) {}
+    val_t(val_t&&) = default;
+    val_t(const val_t&) = delete;
+    val_t& operator = (val_t&&) = default;
+    val_t& operator = (const val_t&) = delete;
+
+    val_t(ValUndef v) : ValVariantBase(std::move(v)) {}
+    val_t(ValInt v) : ValVariantBase(std::move(v)) {}
+    val_t(ValDir v) : ValVariantBase(std::move(v)) {}
+    val_t(ValString v) : ValVariantBase(std::move(v)) {}
+    val_t(ValEntityInt v) : ValVariantBase(std::move(v)) {}
+    val_t(ValEntityPtr v) : ValVariantBase(std::move(v)) {}
+    val_t(ValLocation v) : ValVariantBase(std::move(v)) {}
+    val_t(ValArea v) : ValVariantBase(std::move(v)) {}
+    val_t(ValSpell v) : ValVariantBase(std::move(v)) {}
+    val_t(ValInvocationInt v) : ValVariantBase(std::move(v)) {}
+    val_t(ValInvocationPtr v) : ValVariantBase(std::move(v)) {}
+    val_t(ValFail v) : ValVariantBase(std::move(v)) {}
+    val_t(ValNegative1 v) : ValVariantBase(std::move(v)) {}
+};
+
 
 /* ----------- */
 /* Expressions */
@@ -108,115 +188,187 @@ struct val_t
 
 #define MAX_ARGS 7              /* Max. # of args used in builtin primitive functions */
 
+struct e_area_t;
+
 struct e_location_t
 {
     dumb_ptr<expr_t> m, x, y;
+
+    e_location_t() noexcept : m(), x(), y() {}
+};
+struct ExprAreaUnion
+{
+    dumb_ptr<e_area_t> a_union[2];
+};
+struct ExprAreaRect
+{
+    e_location_t loc;
+    dumb_ptr<expr_t> width, height;
+};
+struct ExprAreaBar
+{
+    e_location_t loc;
+    dumb_ptr<expr_t> width, depth, dir;
 };
 
-struct e_area_t
+using ExprAreaVariantBase = Variant<
+    e_location_t,
+    ExprAreaUnion,
+    ExprAreaRect,
+    ExprAreaBar
+>;
+
+struct e_area_t : ExprAreaVariantBase
 {
-    union a0
-    {
-        e_location_t a_loc;
-        struct
-        {
-            e_location_t loc;
-            dumb_ptr<expr_t> width, depth, dir;
-        } a_bar;
-        struct
-        {
-            e_location_t loc;
-            dumb_ptr<expr_t> width, height;
-        } a_rect;
-        dumb_ptr<e_area_t> a_union[2];
-
-        a0() { really_memzero_this(this); }
-        ~a0() = default;
-        a0(const a0&) = default;
-        a0& operator = (const a0&) = default;
-    } a;
-    AREA ty;
-};
-
-struct expr_t
-{
-    union eu
-    {
-        val_t e_val;
-        e_location_t e_location;
-        e_area_t e_area;
-        struct
-        {
-            fun_t *funp;
-            int line_nr, column;
-            int args_nr;
-            dumb_ptr<expr_t> args[MAX_ARGS];
-        } e_funapp;
-        int e_id;
-        struct
-        {
-            dumb_ptr<expr_t> expr;
-            int id;
-        } e_field;
-
-        eu() { really_memzero_this(this); }
-        ~eu() = default;
-        eu(const eu&) = default;
-        eu& operator = (const eu&) = default;
-    } e;
-    EXPR ty;
-};
-
-struct effect_t
+    e_area_t() = delete;
+    e_area_t(e_area_t&&) = default;
+    e_area_t(const e_area_t&) = delete;
+    e_area_t& operator = (e_area_t&&) = default;
+    e_area_t& operator = (const e_area_t&) = delete;
+
+    e_area_t(e_location_t v) : ExprAreaVariantBase(std::move(v)) {}
+    e_area_t(ExprAreaUnion v) : ExprAreaVariantBase(std::move(v)) {}
+    e_area_t(ExprAreaRect v) : ExprAreaVariantBase(std::move(v)) {}
+    e_area_t(ExprAreaBar v) : ExprAreaVariantBase(std::move(v)) {}
+};
+
+struct ExprFunApp
+{
+    fun_t *funp;
+    int line_nr, column;
+    int args_nr;
+    dumb_ptr<expr_t> args[MAX_ARGS];
+};
+struct ExprId
+{
+    int e_id;
+};
+struct ExprField
+{
+    dumb_ptr<expr_t> expr;
+    int id;
+};
+
+using ExprVariantBase = Variant<
+    val_t,
+    e_location_t,
+    e_area_t,
+    ExprFunApp,
+    ExprId,
+    ExprField
+>;
+struct expr_t : ExprVariantBase
+{
+    expr_t() = delete;
+    expr_t(expr_t&&) = default;
+    expr_t(const expr_t&) = delete;
+    expr_t& operator = (expr_t&&) = default;
+    expr_t& operator = (const expr_t&) = delete;
+
+    expr_t(val_t v) : ExprVariantBase(std::move(v)) {}
+    expr_t(e_location_t v) : ExprVariantBase(std::move(v)) {}
+    expr_t(e_area_t v) : ExprVariantBase(std::move(v)) {}
+    expr_t(ExprFunApp v) : ExprVariantBase(std::move(v)) {}
+    expr_t(ExprId v) : ExprVariantBase(std::move(v)) {}
+    expr_t(ExprField v) : ExprVariantBase(std::move(v)) {}
+};
+
+
+struct effect_t;
+
+struct EffectSkip
+{
+};
+struct EffectAbort
+{
+};
+struct EffectAssign
+{
+    int id;
+    dumb_ptr<expr_t> expr;
+};
+struct EffectForEach
+{
+    int id;
+    dumb_ptr<expr_t> area;
+    dumb_ptr<effect_t> body;
+    FOREACH_FILTER filter;
+};
+struct EffectFor
+{
+    int id;
+    dumb_ptr<expr_t> start, stop;
+    dumb_ptr<effect_t> body;
+};
+struct EffectIf
+{
+    dumb_ptr<expr_t> cond;
+    dumb_ptr<effect_t> true_branch, false_branch;
+};
+struct EffectSleep
+{
+    dumb_ptr<expr_t> e_sleep;        /* sleep time */
+};
+struct EffectScript
+{
+    dumb_ptr<const ScriptBuffer> e_script;
+};
+struct EffectBreak
+{
+};
+struct EffectOp
+{
+    op_t *opp;
+    int args_nr;
+    int line_nr, column;
+    dumb_ptr<expr_t> args[MAX_ARGS];
+};
+struct EffectEnd
+{
+};
+struct EffectCall
+{
+    std::vector<int> *formalv;
+    dumb_ptr<std::vector<dumb_ptr<expr_t>>> actualvp;
+    dumb_ptr<effect_t> body;
+};
+
+using EffectVariantBase = Variant<
+    EffectSkip,
+    EffectAbort,
+    EffectAssign,
+    EffectForEach,
+    EffectFor,
+    EffectIf,
+    EffectSleep,
+    EffectScript,
+    EffectBreak,
+    EffectOp,
+    EffectEnd,
+    EffectCall
+>;
+struct effect_t : EffectVariantBase
 {
     dumb_ptr<effect_t> next;
-    union e0
-    {
-        struct
-        {
-            int id;
-            dumb_ptr<expr_t> expr;
-        } e_assign;
-        struct
-        {
-            int id;
-            dumb_ptr<expr_t> area;
-            dumb_ptr<effect_t> body;
-            FOREACH_FILTER filter;
-        } e_foreach;
-        struct
-        {
-            int id;
-            dumb_ptr<expr_t> start, stop;
-            dumb_ptr<effect_t> body;
-        } e_for;
-        struct
-        {
-            dumb_ptr<expr_t> cond;
-            dumb_ptr<effect_t> true_branch, false_branch;
-        } e_if;
-        dumb_ptr<expr_t> e_sleep;        /* sleep time */
-        dumb_ptr<const ScriptBuffer> e_script;
-        struct
-        {
-            op_t *opp;
-            int args_nr;
-            int line_nr, column;
-            dumb_ptr<expr_t> args[MAX_ARGS];
-        } e_op;
-        struct
-        {
-            std::vector<int> *formalv;
-            dumb_ptr<std::vector<dumb_ptr<expr_t>>> actualvp;
-            dumb_ptr<effect_t> body;
-        } e_call;
-
-        e0() { really_memzero_this(this); }
-        ~e0() = default;
-        e0(const e0&) = default;
-        e0& operator = (const e0&) = default;
-    } e;
-    EFFECT ty;
+
+    effect_t() = delete;
+    effect_t(effect_t&&) = default;
+    effect_t(const effect_t&) = delete;
+    effect_t& operator = (effect_t&&) = default;
+    effect_t& operator = (const effect_t&) = delete;
+
+    effect_t(EffectSkip v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
+    effect_t(EffectAbort v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
+    effect_t(EffectAssign v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
+    effect_t(EffectForEach v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
+    effect_t(EffectFor v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
+    effect_t(EffectIf v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
+    effect_t(EffectSleep v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
+    effect_t(EffectScript v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
+    effect_t(EffectBreak v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
+    effect_t(EffectOp v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
+    effect_t(EffectEnd v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
+    effect_t(EffectCall v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
 };
 
 /* ---------- */
@@ -231,29 +383,62 @@ struct component_t
 };
 
 
+struct spellguard_t;
+struct GuardCondition
+{
+    dumb_ptr<expr_t> s_condition;
+};
+struct GuardMana
+{
+    dumb_ptr<expr_t> s_mana;
+};
+struct GuardCastTime
+{
+    dumb_ptr<expr_t> s_casttime;
+};
+struct GuardComponents
+{
+    dumb_ptr<component_t> s_components;
+};
+struct GuardCatalysts
+{
+    dumb_ptr<component_t> s_catalysts;
+};
+struct GuardChoice
+{
+    dumb_ptr<spellguard_t> s_alt;   /* either `next' or `s.s_alt' */
+};
 struct effect_set_t
 {
     dumb_ptr<effect_t> effect, at_trigger, at_end;
 };
 
-struct spellguard_t
+using SpellGuardVariantBase = Variant<
+    GuardCondition,
+    GuardMana,
+    GuardCastTime,
+    GuardComponents,
+    GuardCatalysts,
+    GuardChoice,
+    effect_set_t
+>;
+struct spellguard_t : SpellGuardVariantBase
 {
     dumb_ptr<spellguard_t> next;
-    union su
-    {
-        dumb_ptr<expr_t> s_condition;
-        dumb_ptr<expr_t> s_mana;
-        dumb_ptr<expr_t> s_casttime;
-        dumb_ptr<component_t> s_components;
-        dumb_ptr<component_t> s_catalysts;
-        dumb_ptr<spellguard_t> s_alt;   /* either `next' or `s.s_alt' */
-        effect_set_t s_effect;
-        su() { really_memzero_this(this); }
-        ~su() = default;
-        su(const su&) = default;
-        su& operator = (const su&) = default;
-    } s;
-    SPELLGUARD ty;
+
+    spellguard_t() = delete;
+    spellguard_t(spellguard_t&&) = default;
+    spellguard_t(const spellguard_t&) = delete;
+    spellguard_t& operator = (spellguard_t&&) = default;
+    spellguard_t& operator = (const spellguard_t&) = delete;
+
+    spellguard_t(GuardCondition v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {}
+    spellguard_t(GuardMana v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {}
+    spellguard_t(GuardCastTime v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {}
+    spellguard_t(GuardComponents v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {}
+    spellguard_t(GuardCatalysts v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {}
+    spellguard_t(GuardChoice v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {}
+    spellguard_t(effect_set_t v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {}
 };
 
 /* ------ */
@@ -330,7 +515,7 @@ struct env_t
     val_t& VAR(size_t i)
     {
         assert (varu);
-        if (varu[i].ty == TYPE::UNDEF)
+        if (varu[i].is<ValUndef>())
             return base_env->varv[i].val;
         else
             return varu[i];
@@ -338,41 +523,47 @@ struct env_t
 
 };
 
-#define MAX_STACK_SIZE 32
+struct CarForEach
+{
+    int id;
+    bool ty_is_spell_not_entity;
+    dumb_ptr<effect_t> body;
+    dumb_ptr<std::vector<BlockId>> entities_vp;
+    int index;
+};
+struct CarFor
+{
+    int id;
+    dumb_ptr<effect_t> body;
+    int current;
+    int stop;
+};
+struct CarProc
+{
+    int args_nr;
+    int *formalap;
+    dumb_ptr<val_t[]> old_actualpa;
+};
+
+using CarVariantBase = Variant<
+    CarForEach,
+    CarFor,
+    CarProc
+>;
 
-struct cont_activation_record_t
+struct cont_activation_record_t : CarVariantBase
 {
     dumb_ptr<effect_t> return_location;
-    union cu
-    {
-        struct
-        {
-            int id;
-            TYPE ty;
-            dumb_ptr<effect_t> body;
-            dumb_ptr<std::vector<BlockId>> entities_vp;
-            int index;
-        } c_foreach;
-        struct
-        {
-            int id;
-            dumb_ptr<effect_t> body;
-            int current;
-            int stop;
-        } c_for;
-        struct
-        {
-            int args_nr;
-            int *formalap;
-            dumb_ptr<val_t[]> old_actualpa;
-        } c_proc;
-
-        cu() { really_memzero_this(this); }
-        ~cu() = default;
-        cu(const cu&) = default;
-        cu& operator = (const cu&) = default;
-    } c;
-    CONT_STACK ty;
+
+    cont_activation_record_t() = delete;
+    cont_activation_record_t(cont_activation_record_t&&) = default;
+    cont_activation_record_t(const cont_activation_record_t&) = delete;
+    cont_activation_record_t& operator = (cont_activation_record_t&&) = default;
+    cont_activation_record_t& operator = (const cont_activation_record_t&) = delete;
+
+    cont_activation_record_t(CarForEach v, dumb_ptr<effect_t> rl) : CarVariantBase(std::move(v)), return_location(rl) {}
+    cont_activation_record_t(CarFor v, dumb_ptr<effect_t> rl) : CarVariantBase(std::move(v)), return_location(rl) {}
+    cont_activation_record_t(CarProc v, dumb_ptr<effect_t> rl) : CarVariantBase(std::move(v)), return_location(rl) {}
 };
 
 struct status_change_ref_t
@@ -393,8 +584,7 @@ struct invocation : block_list
 
     Timer timer;                 /* spell timer, if any */
 
-    int stack_size;
-    cont_activation_record_t stack[MAX_STACK_SIZE];
+    std::vector<cont_activation_record_t> stack;
 
     int script_pos;            /* Script position; if nonzero, resume the script we were running. */
     dumb_ptr<effect_t> current_effect;
diff --git a/src/map/magic-interpreter.t.hpp b/src/map/magic-interpreter.t.hpp
index 095922a..ab151fc 100644
--- a/src/map/magic-interpreter.t.hpp
+++ b/src/map/magic-interpreter.t.hpp
@@ -35,58 +35,6 @@ enum class SPELLARG : uint8_t
     STRING,
 };
 
-enum class TYPE : uint8_t
-{
-    UNDEF,
-    INT,
-    DIR,
-    STRING,
-    ENTITY,
-    LOCATION,
-    AREA,
-    SPELL,
-    INVOCATION,
-    FAIL = 127,
-
-    NEGATIVE_1 = 255,
-};
-
-enum class AREA : uint8_t
-{
-    LOCATION,
-    UNION,
-    RECT,
-    BAR,
-};
-
-enum class EXPR : uint8_t
-{
-    VAL,
-    LOCATION,
-    AREA,
-    FUNAPP,
-    ID,
-    SPELLFIELD,
-};
-
-// temporary rename to avoid collision with enum value
-// in magic-interpreter-parser
-enum class EFFECT : uint8_t
-{
-    SKIP,
-    ABORT,
-    ASSIGN,
-    FOREACH,
-    FOR,
-    IF,
-    SLEEP,
-    SCRIPT,
-    BREAK,
-    OP,
-    END,
-    CALL,
-};
-
 enum class FOREACH_FILTER : uint8_t
 {
     MOB,
@@ -97,17 +45,6 @@ enum class FOREACH_FILTER : uint8_t
     NPC,
 };
 
-enum class SPELLGUARD : uint8_t
-{
-    CONDITION,
-    COMPONENTS,
-    CATALYSTS,
-    CHOICE,
-    MANA,
-    CASTTIME,
-    EFFECT,
-};
-
 namespace e
 {
 enum class SPELL_FLAG : uint8_t
@@ -125,13 +62,6 @@ ENUM_BITWISE_OPERATORS(SPELL_FLAG)
 }
 using e::SPELL_FLAG;
 
-enum class CONT_STACK : uint8_t
-{
-    FOREACH,
-    FOR,
-    PROC,
-};
-
 namespace e
 {
 enum class INVOCATION_FLAG : uint8_t
diff --git a/src/map/magic-stmt.cpp b/src/map/magic-stmt.cpp
index 28bcbe8..fd02d45 100644
--- a/src/map/magic-stmt.cpp
+++ b/src/map/magic-stmt.cpp
@@ -55,61 +55,19 @@ namespace magic
 /* used for local spell effects */
 constexpr Species INVISIBLE_NPC = wrap<Species>(127);
 
-//#define DEBUG
-
-#ifdef DEBUG
-static
-void print_val(val_t *v)
-{
-    switch (v->ty)
-    {
-        case TYPE::UNDEF:
-            FPRINTF(stderr, "UNDEF"_fmt);
-            break;
-        case TYPE::INT:
-            FPRINTF(stderr, "%d"_fmt, v->v.v_int);
-            break;
-        case TYPE::DIR:
-            FPRINTF(stderr, "dir%d"_fmt, v->v.v_int);
-            break;
-        case TYPE::STRING:
-            FPRINTF(stderr, "`%s'"_fmt, v->v.v_string);
-            break;
-        default:
-            FPRINTF(stderr, "ty%d"_fmt, v->ty);
-            break;
-    }
-}
-
-static
-void dump_env(env_t *env)
-{
-    int i;
-    for (i = 0; i < env->base_env->vars_nr; i++)
-    {
-        val_t *v = &env->vars[i];
-        val_t *bv = &env->base_env->vars[i];
-
-        FPRINTF(stderr, "%02x %30s "_fmt, i, env->base_env->var_name[i]);
-        print_val(v);
-        FPRINTF(stderr, "\t("_fmt);
-        print_val(bv);
-        FPRINTF(stderr, ")\n"_fmt);
-    }
-}
-#endif
-
 static
 void clear_activation_record(cont_activation_record_t *ar)
 {
-    switch (ar->ty)
+    MATCH (*ar)
     {
-        case CONT_STACK::FOREACH:
-            ar->c.c_foreach.entities_vp.delete_();
-            break;
-        case CONT_STACK::PROC:
-            ar->c.c_proc.old_actualpa.delete_();
-            break;
+        CASE (CarForEach&, c_foreach)
+        {
+            c_foreach.entities_vp.delete_();
+        }
+        CASE (CarProc&, c_proc)
+        {
+            c_proc.old_actualpa.delete_();
+        }
     }
 }
 
@@ -129,10 +87,10 @@ void clear_stack(dumb_ptr<invocation> invocation_)
 {
     int i;
 
-    for (i = 0; i < invocation_->stack_size; i++)
+    for (i = 0; i < invocation_->stack.size(); i++)
         clear_activation_record(&invocation_->stack[i]);
 
-    invocation_->stack_size = 0;
+    invocation_->stack.clear();
 }
 
 void spell_free_invocation(dumb_ptr<invocation> invocation_)
@@ -254,8 +212,7 @@ BlockId trigger_spell(BlockId subject, BlockId spell)
 
     spell_bind(map_id_is_player(subject), invocation_);
     magic_clear_var(&invocation_->env->varu[VAR_CASTER]);
-    invocation_->env->varu[VAR_CASTER].ty = TYPE::ENTITY;
-    invocation_->env->varu[VAR_CASTER].v.v_int = static_cast<int32_t>(unwrap<BlockId>(subject));
+    invocation_->env->varu[VAR_CASTER] = ValEntityInt{subject};
 
     return invocation_->bl_id;
 }
@@ -327,11 +284,11 @@ int op_sfx(dumb_ptr<env_t>, Slice<val_t> args)
 {
     interval_t delay = static_cast<interval_t>(ARGINT(2));
 
-    if (ARG_TYPE(0) == TYPE::ENTITY)
+    if (args[0].is<ValEntityPtr>())
     {
         entity_effect(ARGENTITY(0), ARGINT(1), delay);
     }
-    else if (ARG_TYPE(0) == TYPE::LOCATION)
+    else if (args[0].is<ValLocation>())
     {
         local_spell_effect(ARGLOCATION(0).m,
                             ARGLOCATION(0).x,
@@ -346,8 +303,10 @@ int op_sfx(dumb_ptr<env_t>, Slice<val_t> args)
 static
 int op_instaheal(dumb_ptr<env_t> env, Slice<val_t> args)
 {
-    dumb_ptr<block_list> caster = (env->VAR(VAR_CASTER).ty == TYPE::ENTITY)
-        ? map_id2bl(wrap<BlockId>(static_cast<uint32_t>(env->VAR(VAR_CASTER).v.v_int))) : nullptr;
+    assert (!env->VAR(VAR_CASTER).is<ValEntityPtr>());
+    ValEntityInt *caster_id = env->VAR(VAR_CASTER).get_if<ValEntityInt>();
+    dumb_ptr<block_list> caster = caster_id
+        ? map_id2bl(caster_id->v_eid) : nullptr;
     dumb_ptr<block_list> subject = ARGENTITY(0);
     if (!caster)
         caster = subject;
@@ -557,8 +516,10 @@ static
 int op_status_change(dumb_ptr<env_t> env, Slice<val_t> args)
 {
     dumb_ptr<block_list> subject = ARGENTITY(0);
-    BlockId invocation_id = env->VAR(VAR_INVOCATION).ty == TYPE::INVOCATION
-        ? wrap<BlockId>(static_cast<uint32_t>(env->VAR(VAR_INVOCATION).v.v_int)) : BlockId();
+    assert (!env->VAR(VAR_INVOCATION).is<ValInvocationPtr>());
+    ValInvocationInt *ii = env->VAR(VAR_INVOCATION).get_if<ValInvocationInt>();
+    BlockId invocation_id = ii
+        ? ii->v_iid : BlockId();
     dumb_ptr<invocation> invocation_ = map_id_is_spell(invocation_id);
 
     assert (!ARGINT(3));
@@ -609,8 +570,9 @@ int op_override_attack(dumb_ptr<env_t> env, Slice<val_t> args)
             spell_free_invocation(old_invocation);
     }
 
+    ValInvocationInt *ii = env->VAR(VAR_INVOCATION).get_if<ValInvocationInt>();
     subject->attack_spell_override =
-        trigger_spell(subject->bl_id, wrap<BlockId>(static_cast<uint32_t>(env->VAR(VAR_INVOCATION).v.v_int)));
+        trigger_spell(subject->bl_id, ii->v_iid);
     subject->attack_spell_charges = charges;
 
     if (subject->attack_spell_override)
@@ -778,11 +740,14 @@ int op_spawn(dumb_ptr<env_t>, Slice<val_t> args)
 static
 ZString get_invocation_name(dumb_ptr<env_t> env)
 {
-    dumb_ptr<invocation> invocation_;
+    assert (!env->VAR(VAR_INVOCATION).is<ValInvocationPtr>());
 
-    if (env->VAR(VAR_INVOCATION).ty != TYPE::INVOCATION)
+    ValInvocationInt *ii = env->VAR(VAR_INVOCATION).get_if<ValInvocationInt>();
+    if (!ii)
         return "?"_s;
-    invocation_ = map_id_is_spell(wrap<BlockId>(static_cast<uint32_t>(env->VAR(VAR_INVOCATION).v.v_int)));
+
+    dumb_ptr<invocation> invocation_;
+    invocation_ = map_id_is_spell(ii->v_iid);
 
     if (invocation_)
         return invocation_->spell->name;
@@ -945,7 +910,7 @@ int op_gain_exp(dumb_ptr<env_t>, Slice<val_t> args)
         return 1;
 
     pc_gainexp_reason(c, ARGINT(1), ARGINT(2),
-            PC_GAINEXP_REASON(ARGINT(3)));
+            static_cast<PC_GAINEXP_REASON>(ARGINT(3)));
     return 0;
 }
 
@@ -1024,108 +989,83 @@ void spell_effect_report_termination(BlockId invocation_id, BlockId bl_id,
 static
 dumb_ptr<effect_t> return_to_stack(dumb_ptr<invocation> invocation_)
 {
-    if (!invocation_->stack_size)
+    if (invocation_->stack.empty())
         return nullptr;
     else
     {
         cont_activation_record_t *ar =
-            invocation_->stack + (invocation_->stack_size - 1);
-        switch (ar->ty)
+            &invocation_->stack.back();
+        MATCH (*ar)
         {
-            case CONT_STACK::PROC:
+            CASE (const CarProc&, c_proc)
             {
                 dumb_ptr<effect_t> ret = ar->return_location;
-                for (int i = 0; i < ar->c.c_proc.args_nr; i++)
+                for (int i = 0; i < c_proc.args_nr; i++)
                 {
                     val_t *var =
-                        &invocation_->env->varu[ar->c.c_proc.formalap[i]];
+                        &invocation_->env->varu[c_proc.formalap[i]];
                     magic_clear_var(var);
-                    *var = ar->c.c_proc.old_actualpa[i];
+                    *var = std::move(c_proc.old_actualpa[i]);
                 }
 
                 // pop the stack
                 clear_activation_record(ar);
-                --invocation_->stack_size;
+                invocation_->stack.pop_back();
 
                 return ret;
             }
-
-            case CONT_STACK::FOREACH:
+            CASE (CarForEach&, c_foreach)
             {
                 BlockId entity_id;
-                val_t *var = &invocation_->env->varu[ar->c.c_foreach.id];
+                val_t *var = &invocation_->env->varu[c_foreach.id];
 
                 do
                 {
                     // This >= is really supposed to be a ==, but
                     // I have no clue if it's actually safe to change it.
-                    if (ar->c.c_foreach.index >= ar->c.c_foreach.entities_vp->size())
+                    if (c_foreach.index >= c_foreach.entities_vp->size())
                     {
                         // pop the stack
                         dumb_ptr<effect_t> ret = ar->return_location;
                         clear_activation_record(ar);
-                        --invocation_->stack_size;
+                        invocation_->stack.pop_back();
                         return ret;
                     }
 
                     entity_id =
-                        (*ar->c.c_foreach.entities_vp)[ar->c.c_foreach.index++];
+                        (*c_foreach.entities_vp)[c_foreach.index++];
                 }
                 while (!entity_id || !map_id2bl(entity_id));
 
                 magic_clear_var(var);
-                var->ty = ar->c.c_foreach.ty;
-                var->v.v_int = static_cast<int32_t>(unwrap<BlockId>(entity_id));
+                if (c_foreach.ty_is_spell_not_entity)
+                    *var = ValInvocationInt{entity_id};
+                else
+                    *var = ValEntityInt{entity_id};
 
-                return ar->c.c_foreach.body;
+                return c_foreach.body;
             }
-
-            case CONT_STACK::FOR:
-                if (ar->c.c_for.current > ar->c.c_for.stop)
+            CASE (CarFor&, c_for)
+            {
+                if (c_for.current > c_for.stop)
                 {
                     dumb_ptr<effect_t> ret = ar->return_location;
                     // pop the stack
                     clear_activation_record(ar);
-                    --invocation_->stack_size;
+                    invocation_->stack.pop_back();
                     return ret;
                 }
 
-                magic_clear_var(&invocation_->env->varu[ar->c.c_for.id]);
-                invocation_->env->varu[ar->c.c_for.id].ty = TYPE::INT;
-                invocation_->env->varu[ar->c.c_for.id].v.v_int =
-                    ar->c.c_for.current++;
+                magic_clear_var(&invocation_->env->varu[c_for.id]);
+                invocation_->env->varu[c_for.id] = ValInt{c_for.current++};
 
-                return ar->c.c_for.body;
-
-            default:
-                FPRINTF(stderr,
-                        "[magic] INTERNAL ERROR: While executing spell `%s':  stack corruption\n"_fmt,
-                        invocation_->spell->name);
-                return nullptr;
+                return c_for.body;
+            }
         }
+        abort();
     }
 }
 
-static
-cont_activation_record_t *add_stack_entry(dumb_ptr<invocation> invocation_,
-        CONT_STACK ty, dumb_ptr<effect_t> return_location)
-{
-    cont_activation_record_t *ar =
-        invocation_->stack + invocation_->stack_size++;
-    if (invocation_->stack_size >= MAX_STACK_SIZE)
-    {
-        FPRINTF(stderr,
-                "[magic] Execution stack size exceeded in spell `%s'; truncating effect\n"_fmt,
-                invocation_->spell->name);
-        invocation_->stack_size--;
-        return nullptr;
-    }
-
-    ar->ty = ty;
-    ar->return_location = return_location;
-    return ar;
-}
-
 static
 void find_entities_in_area_c(dumb_ptr<block_list> target,
         std::vector<BlockId> *entities_vp,
@@ -1193,19 +1133,46 @@ void find_entities_in_area(area_t& area_,
         std::vector<BlockId> *entities_vp,
         FOREACH_FILTER filter)
 {
-    area_t *area = &area_; // temporary hack to "keep diff small". Heh.
-    switch (area->ty)
+    MATCH (area_)
     {
-        case AREA::UNION:
-            find_entities_in_area(*area->a.a_union[0], entities_vp, filter);
-            find_entities_in_area(*area->a.a_union[1], entities_vp, filter);
-            break;
-
-        default:
+        CASE (const AreaUnion&, a)
+        {
+            find_entities_in_area(*a.a_union[0], entities_vp, filter);
+            find_entities_in_area(*a.a_union[1], entities_vp, filter);
+        }
+        CASE (const location_t&, a_loc)
         {
+            (void)a_loc;
+            // TODO this can be simplified
             map_local *m;
             int x, y, width, height;
-            magic_area_rect(&m, &x, &y, &width, &height, *area);
+            magic_area_rect(&m, &x, &y, &width, &height, area_);
+            map_foreachinarea(std::bind(find_entities_in_area_c, ph::_1, entities_vp, filter),
+                    m,
+                    x, y,
+                    x + width, y + height,
+                    BL::NUL /* filter elsewhere */);
+        }
+        CASE (const AreaRect&, a_rect)
+        {
+            (void)a_rect;
+            // TODO this can be simplified
+            map_local *m;
+            int x, y, width, height;
+            magic_area_rect(&m, &x, &y, &width, &height, area_);
+            map_foreachinarea(std::bind(find_entities_in_area_c, ph::_1, entities_vp, filter),
+                    m,
+                    x, y,
+                    x + width, y + height,
+                    BL::NUL /* filter elsewhere */);
+        }
+        CASE (const AreaBar&, a_bar)
+        {
+            (void)a_bar;
+            // TODO this is wrong
+            map_local *m;
+            int x, y, width, height;
+            magic_area_rect(&m, &x, &y, &width, &height, area_);
             map_foreachinarea(std::bind(find_entities_in_area_c, ph::_1, entities_vp, filter),
                     m,
                     x, y,
@@ -1217,17 +1184,20 @@ void find_entities_in_area(area_t& area_,
 
 static
 dumb_ptr<effect_t> run_foreach(dumb_ptr<invocation> invocation,
-        dumb_ptr<effect_t> foreach,
+        const EffectForEach *foreach,
         dumb_ptr<effect_t> return_location)
 {
+    const EffectForEach& e_foreach = *foreach;
+
     val_t area;
-    FOREACH_FILTER filter = foreach->e.e_foreach.filter;
-    int id = foreach->e.e_foreach.id;
-    dumb_ptr<effect_t> body = foreach->e.e_foreach.body;
+    FOREACH_FILTER filter = e_foreach.filter;
+    int id = e_foreach.id;
+    dumb_ptr<effect_t> body = e_foreach.body;
 
-    magic_eval(invocation->env, &area, foreach->e.e_foreach.area);
+    magic_eval(invocation->env, &area, e_foreach.area);
 
-    if (area.ty != TYPE::AREA)
+    auto va = area.get_if<ValArea>();
+    if (!va)
     {
         magic_clear_var(&area);
         FPRINTF(stderr,
@@ -1235,27 +1205,23 @@ dumb_ptr<effect_t> run_foreach(dumb_ptr<invocation> invocation,
                 invocation->spell->name);
         return return_location;
     }
-    else
-    {
-        cont_activation_record_t *ar =
-            add_stack_entry(invocation, CONT_STACK::FOREACH, return_location);
-
-        if (!ar)
-            return return_location;
 
+    {
         std::vector<BlockId> entities_v;
-        find_entities_in_area(*area.v.v_area,
+        find_entities_in_area(*va->v_area,
                 &entities_v, filter);
         entities_v.shrink_to_fit();
         // iterator_pair will go away when this gets properly containerized.
         random_::shuffle(entities_v);
 
-        ar->c.c_foreach.id = id;
-        ar->c.c_foreach.body = body;
-        ar->c.c_foreach.index = 0;
-        ar->c.c_foreach.entities_vp.new_(std::move(entities_v));
-        ar->c.c_foreach.ty =
-            (filter == FOREACH_FILTER::SPELL) ? TYPE::INVOCATION : TYPE::ENTITY;
+        CarForEach c_foreach;
+        c_foreach.id = id;
+        c_foreach.body = body;
+        c_foreach.index = 0;
+        c_foreach.entities_vp.new_(std::move(entities_v));
+        c_foreach.ty_is_spell_not_entity =
+            (filter == FOREACH_FILTER::SPELL);
+        invocation->stack.emplace_back(c_foreach, return_location);
 
         magic_clear_var(&area);
 
@@ -1265,18 +1231,19 @@ dumb_ptr<effect_t> run_foreach(dumb_ptr<invocation> invocation,
 
 static
 dumb_ptr<effect_t> run_for (dumb_ptr<invocation> invocation,
-        dumb_ptr<effect_t> for_,
+        const EffectFor *for_,
         dumb_ptr<effect_t> return_location)
 {
-    cont_activation_record_t *ar;
-    int id = for_->e.e_for.id;
+    const EffectFor& e_for = *for_;
+
+    int id = e_for.id;
     val_t start;
     val_t stop;
 
-    magic_eval(invocation->env, &start, for_->e.e_for.start);
-    magic_eval(invocation->env, &stop, for_->e.e_for.stop);
+    magic_eval(invocation->env, &start, e_for.start);
+    magic_eval(invocation->env, &stop, e_for.stop);
 
-    if (start.ty != TYPE::INT || stop.ty != TYPE::INT)
+    if (!start.is<ValInt>() || !stop.is<ValInt>())
     {
         magic_clear_var(&start);
         magic_clear_var(&stop);
@@ -1286,109 +1253,44 @@ dumb_ptr<effect_t> run_for (dumb_ptr<invocation> invocation,
         return return_location;
     }
 
-    ar = add_stack_entry(invocation, CONT_STACK::FOR, return_location);
-
-    if (!ar)
-        return return_location;
+    CarFor c_for;
 
-    ar->c.c_for.id = id;
-    ar->c.c_for.current = start.v.v_int;
-    ar->c.c_for.stop = stop.v.v_int;
-    ar->c.c_for.body = for_->e.e_for.body;
+    c_for.id = id;
+    c_for.current = start.get_if<ValInt>()->v_int;
+    c_for.stop = stop.get_if<ValInt>()->v_int;
+    c_for.body = e_for.body;
+    invocation->stack.emplace_back(c_for, return_location);
 
     return return_to_stack(invocation);
 }
 
 static
 dumb_ptr<effect_t> run_call(dumb_ptr<invocation> invocation,
+        const EffectCall *call,
         dumb_ptr<effect_t> return_location)
 {
-    dumb_ptr<effect_t> current = invocation->current_effect;
-    cont_activation_record_t *ar;
-    int args_nr = current->e.e_call.formalv->size();
-    int *formals = current->e.e_call.formalv->data();
+    const EffectCall& e_call = *call;
+
+    int args_nr = e_call.formalv->size();
+    int *formals = e_call.formalv->data();
     auto old_actuals = dumb_ptr<val_t[]>::make(args_nr);
 
-    ar = add_stack_entry(invocation, CONT_STACK::PROC, return_location);
-    ar->c.c_proc.args_nr = args_nr;
-    ar->c.c_proc.formalap = formals;
-    ar->c.c_proc.old_actualpa = old_actuals;
+    CarProc c_proc;
+    c_proc.args_nr = args_nr;
+    c_proc.formalap = formals;
+    c_proc.old_actualpa = old_actuals;
+    invocation->stack.emplace_back(c_proc, return_location);
+
     for (int i = 0; i < args_nr; i++)
     {
         val_t *env_val = &invocation->env->varu[formals[i]];
         magic_copy_var(&old_actuals[i], env_val);
-        magic_eval(invocation->env, env_val, (*current->e.e_call.actualvp)[i]);
+        magic_eval(invocation->env, env_val, (*e_call.actualvp)[i]);
     }
 
-    return current->e.e_call.body;
+    return e_call.body;
 }
 
-#ifdef DEBUG
-static
-void print_cfg(int i, effect_t *e)
-{
-    int j;
-    for (j = 0; j < i; j++)
-        PRINTF("    "_fmt);
-
-    PRINTF("%p: "_fmt, e);
-
-    if (!e)
-    {
-        puts(" -- end --");
-        return;
-    }
-
-    switch (e->ty)
-    {
-        case EFFECT::SKIP:
-            puts("SKIP");
-            break;
-        case EFFECT::END:
-            puts("END");
-            break;
-        case EFFECT::ABORT:
-            puts("ABORT");
-            break;
-        case EFFECT::ASSIGN:
-            puts("ASSIGN");
-            break;
-        case EFFECT::FOREACH:
-            puts("FOREACH");
-            print_cfg(i + 1, e->e.e_foreach.body);
-            break;
-        case EFFECT::FOR:
-            puts("FOR");
-            print_cfg(i + 1, e->e.e_for.body);
-            break;
-        case EFFECT::IF:
-            puts("IF");
-            for (j = 0; j < i; j++)
-                PRINTF("    "_fmt);
-            puts("THEN");
-            print_cfg(i + 1, e->e.e_if.true_branch);
-            for (j = 0; j < i; j++)
-                PRINTF("    "_fmt);
-            puts("ELSE");
-            print_cfg(i + 1, e->e.e_if.false_branch);
-            break;
-        case EFFECT::SLEEP:
-            puts("SLEEP");
-            break;
-        case EFFECT::SCRIPT:
-            puts("NpcSubtype::SCRIPT");
-            break;
-        case EFFECT::BREAK:
-            puts("BREAK");
-            break;
-        case EFFECT::OP:
-            puts("OP");
-            break;
-    }
-    print_cfg(i, e->next);
-}
-#endif
-
 /**
  * Execute a spell invocation until we abort, finish, or hit the next `sleep'.
  *
@@ -1404,83 +1306,77 @@ interval_t spell_run(dumb_ptr<invocation> invocation_, int allow_delete)
     const BlockId invocation_id = invocation_->bl_id;
 #define REFRESH_INVOCATION invocation_ = map_id_is_spell(invocation_id); if (!invocation_) return interval_t::zero();
 
-#ifdef DEBUG
-    FPRINTF(stderr, "Resuming execution:  invocation of `%s'\n"_fmt,
-            invocation_->spell->name);
-    print_cfg(1, invocation_->current_effect);
-#endif
     while (invocation_->current_effect)
     {
         dumb_ptr<effect_t> e = invocation_->current_effect;
         dumb_ptr<effect_t> next = e->next;
         int i;
 
-#ifdef DEBUG
-        FPRINTF(stderr, "Next step of type %d\n"_fmt, e->ty);
-        dump_env(invocation_->env);
-#endif
-
-        switch (e->ty)
+        MATCH (*e)
         {
-            case EFFECT::SKIP:
-                break;
-
-            case EFFECT::ABORT:
+            CASE (const EffectSkip&, e_)
+            {
+                (void)e_;
+            }
+            CASE (const EffectAbort&, e_)
+            {
+                (void)e_;
                 invocation_->flags |= INVOCATION_FLAG::ABORTED;
                 invocation_->end_effect = nullptr;
-                FALLTHROUGH;
-            case EFFECT::END:
                 clear_stack(invocation_);
                 next = nullptr;
-                break;
-
-            case EFFECT::ASSIGN:
+            }
+            CASE (const EffectEnd&, e_)
+            {
+                (void)e_;
+                clear_stack(invocation_);
+                next = nullptr;
+            }
+            CASE (const EffectAssign&, e_assign)
+            {
                 magic_eval(invocation_->env,
-                            &invocation_->env->varu[e->e.e_assign.id],
-                            e->e.e_assign.expr);
-                break;
-
-            case EFFECT::FOREACH:
-                next = run_foreach(invocation_, e, next);
-                break;
-
-            case EFFECT::FOR:
-                next = run_for (invocation_, e, next);
-                break;
-
-            case EFFECT::IF:
-                if (magic_eval_int(invocation_->env, e->e.e_if.cond))
-                    next = e->e.e_if.true_branch;
+                            &invocation_->env->varu[e_assign.id],
+                            e_assign.expr);
+            }
+            CASE (const EffectForEach&, e_foreach)
+            {
+                next = run_foreach(invocation_, &e_foreach, next);
+            }
+            CASE (const EffectFor&, e_for)
+            {
+                next = run_for (invocation_, &e_for, next);
+            }
+            CASE (const EffectIf&, e_if)
+            {
+                if (magic_eval_int(invocation_->env, e_if.cond))
+                    next = e_if.true_branch;
                 else
-                    next = e->e.e_if.false_branch;
-                break;
-
-            case EFFECT::SLEEP:
+                    next = e_if.false_branch;
+            }
+            CASE (const EffectSleep&, e_)
             {
                 interval_t sleeptime = static_cast<interval_t>(
-                        magic_eval_int(invocation_->env, e->e.e_sleep));
+                        magic_eval_int(invocation_->env, e_.e_sleep));
                 invocation_->current_effect = next;
                 if (sleeptime > interval_t::zero())
                     return sleeptime;
-                break;
             }
-
-            case EFFECT::SCRIPT:
+            CASE (const EffectScript&, e_)
             {
                 dumb_ptr<map_session_data> caster = map_id_is_player(invocation_->caster);
                 if (caster)
                 {
                     dumb_ptr<env_t> env = invocation_->env;
                     ZString caster_name = (caster ? caster->status_key.name : CharName()).to__actual();
-                    argrec_t arg[3] =
+                    argrec_t arg[1] =
                     {
-                        {"@target"_s, env->VAR(VAR_TARGET).ty == TYPE::ENTITY ? 0 : env->VAR(VAR_TARGET).v.v_int},
-                        {"@caster"_s, static_cast<int32_t>(unwrap<BlockId>(invocation_->caster))},
                         {"@caster_name$"_s, caster_name},
                     };
+                    assert (!env->VAR(VAR_SCRIPTTARGET).is<ValEntityPtr>());
+                    ValEntityInt *ei = env->VAR(VAR_SCRIPTTARGET).get_if<ValEntityInt>();
                     BlockId message_recipient =
-                        env->VAR(VAR_SCRIPTTARGET).ty == TYPE::ENTITY
-                        ? wrap<BlockId>(static_cast<uint32_t>(env->VAR(VAR_SCRIPTTARGET).v.v_int))
+                        ei
+                        ? ei->v_eid
                         : invocation_->caster;
                     dumb_ptr<map_session_data> recipient = map_id_is_player(message_recipient);
 
@@ -1495,7 +1391,7 @@ interval_t spell_run(dumb_ptr<invocation> invocation_, int allow_delete)
                     // dealing with an NPC
 
                     int newpos = run_script_l(
-                            ScriptPointer(&*e->e.e_script, invocation_->script_pos),
+                            ScriptPointer(&*e_.e_script, invocation_->script_pos),
                             message_recipient, invocation_->bl_id,
                             arg);
                     /* Returns the new script position, or -1 once the script is finished */
@@ -1511,42 +1407,35 @@ interval_t spell_run(dumb_ptr<invocation> invocation_, int allow_delete)
                     clif_clearchar_id(invocation_->bl_id, BeingRemoveWhy::DEAD, caster->sess);
                 }
                 REFRESH_INVOCATION; // Script may have killed the caster
-                break;
             }
-
-            case EFFECT::BREAK:
+            CASE (const EffectBreak&, e_)
+            {
+                (void)e_;
                 next = return_to_stack(invocation_);
-                break;
-
-            case EFFECT::OP:
+            }
+            CASE (const EffectOp&, e_op)
             {
-                op_t *op = e->e.e_op.opp;
+                op_t *op = e_op.opp;
                 val_t args[MAX_ARGS];
 
-                for (i = 0; i < e->e.e_op.args_nr; i++)
-                    magic_eval(invocation_->env, &args[i], e->e.e_op.args[i]);
+                for (i = 0; i < e_op.args_nr; i++)
+                    magic_eval(invocation_->env, &args[i], e_op.args[i]);
 
                 if (!magic_signature_check("effect"_s, op->name, op->signature,
-                                            Slice<val_t>(args, e->e.e_op.args_nr),
-                                            e->e.e_op.line_nr,
-                                            e->e.e_op.column))
-                    op->op(invocation_->env, Slice<val_t>(args, e->e.e_op.args_nr));
+                                            Slice<val_t>(args, e_op.args_nr),
+                                            e_op.line_nr,
+                                            e_op.column))
+                    op->op(invocation_->env, Slice<val_t>(args, e_op.args_nr));
 
-                for (i = 0; i < e->e.e_op.args_nr; i++)
+                for (i = 0; i < e_op.args_nr; i++)
                     magic_clear_var(&args[i]);
 
                 REFRESH_INVOCATION; // Effect may have killed the caster
-                break;
             }
-
-            case EFFECT::CALL:
-                next = run_call(invocation_, next);
-                break;
-
-            default:
-                FPRINTF(stderr,
-                        "[magic] INTERNAL ERROR: Unknown effect %d\n"_fmt,
-                        e->ty);
+            CASE (const EffectCall&, e_call)
+            {
+                next = run_call(invocation_, &e_call, next);
+            }
         }
 
         if (!next)
@@ -1610,8 +1499,7 @@ int spell_attack(BlockId caster_id, BlockId target_id)
     if (invocation_ && caster->attack_spell_charges > 0)
     {
         magic_clear_var(&invocation_->env->varu[VAR_TARGET]);
-        invocation_->env->varu[VAR_TARGET].ty = TYPE::ENTITY;
-        invocation_->env->varu[VAR_TARGET].v.v_int = static_cast<int32_t>(unwrap<BlockId>(target_id));
+        invocation_->env->varu[VAR_TARGET] = ValEntityInt{target_id};
 
         invocation_->current_effect = invocation_->trigger_effect;
         invocation_->flags &= ~INVOCATION_FLAG::ABORTED;
diff --git a/src/map/magic-v2.cpp b/src/map/magic-v2.cpp
index fe135ea..73b7534 100644
--- a/src/map/magic-v2.cpp
+++ b/src/map/magic-v2.cpp
@@ -67,8 +67,8 @@ namespace magic_v2
         /* Must add new */
         magic_conf_t::mcvar new_var {};
         new_var.name = id_name;
-        new_var.val.ty = TYPE::UNDEF;
-        magic_conf.varv.push_back(new_var);
+        new_var.val = ValUndef();
+        magic_conf.varv.push_back(std::move(new_var));
 
         return i;
     }
@@ -105,9 +105,9 @@ namespace magic_v2
 
 
     static
-    bool bind_constant(io::LineSpan span, RString name, val_t *val)
+    bool bind_constant(io::LineSpan span, RString name, val_t val)
     {
-        if (!const_defm.insert({name, *val}).second)
+        if (!const_defm.insert(std::make_pair(name, std::move(val))).second)
         {
             span.error(STRPRINTF("Redefinition of constant '%s'"_fmt, name));
             return false;
@@ -115,7 +115,7 @@ namespace magic_v2
         return true;
     }
     static
-    val_t *find_constant(RString name)
+    const val_t *find_constant(RString name)
     {
         auto it = const_defm.find(name);
         if (it != const_defm.end())
@@ -124,13 +124,6 @@ namespace magic_v2
         return nullptr;
     }
     static
-    dumb_ptr<effect_t> new_effect(EFFECT ty)
-    {
-        auto effect = dumb_ptr<effect_t>::make();
-        effect->ty = ty;
-        return effect;
-    }
-    static
     dumb_ptr<effect_t> set_effect_continuation(dumb_ptr<effect_t> src, dumb_ptr<effect_t> continuation)
     {
         dumb_ptr<effect_t> retval = src;
@@ -141,10 +134,13 @@ namespace magic_v2
 
         /* For FOR and FOREACH, we use special stack handlers and thus don't have to set
          * the continuation.  It's only IF that we need to handle in this fashion. */
-        if (src->ty == EFFECT::IF)
+        MATCH (*src)
         {
-            set_effect_continuation(src->e.e_if.true_branch, continuation);
-            set_effect_continuation(src->e.e_if.false_branch, continuation);
+            CASE (EffectIf&, e_if)
+            {
+                set_effect_continuation(e_if.true_branch, continuation);
+                set_effect_continuation(e_if.false_branch, continuation);
+            }
         }
 
         if (src->next)
@@ -155,13 +151,6 @@ namespace magic_v2
         return retval;
     }
     static
-    dumb_ptr<spellguard_t> new_spellguard(SPELLGUARD ty)
-    {
-        dumb_ptr<spellguard_t> retval = dumb_ptr<spellguard_t>::make();
-        retval->ty = ty;
-        return retval;
-    }
-    static
     dumb_ptr<spellguard_t> spellguard_implication(dumb_ptr<spellguard_t> a, dumb_ptr<spellguard_t> b)
     {
         dumb_ptr<spellguard_t> retval = a;
@@ -183,8 +172,13 @@ namespace magic_v2
         }
 
         /* If the premise is a disjunction, b is the continuation of _all_ branches */
-        if (a->ty == SPELLGUARD::CHOICE)
-            spellguard_implication(a->s.s_alt, b);
+        MATCH (*a)
+        {
+            CASE(const GuardChoice&, s)
+            {
+                spellguard_implication(s.s_alt, b);
+            }
+        }
 
         if (a->next)
             spellguard_implication(a->next, b);
@@ -265,10 +259,11 @@ namespace magic_v2
             return false;
         }
 
-        retval = new_effect(EFFECT::CALL);
-        retval->e.e_call.body = p->body;
-        retval->e.e_call.formalv = &p->argv;
-        retval->e.e_call.actualvp = argvp;
+        EffectCall e_call;
+        e_call.body = p->body;
+        e_call.formalv = &p->argv;
+        e_call.actualvp = argvp;
+        retval = dumb_ptr<effect_t>::make(e_call, nullptr);
         return true;
     }
     static
@@ -287,23 +282,25 @@ namespace magic_v2
             return false;
         }
 
-        effect = new_effect(EFFECT::OP);
-        effect->e.e_op.line_nr = span.begin.line;
-        effect->e.e_op.column = span.begin.column;
-        effect->e.e_op.opp = op;
+        EffectOp e_op;
+        e_op.line_nr = span.begin.line;
+        e_op.column = span.begin.column;
+        e_op.opp = op;
         assert (argv.size() <= MAX_ARGS);
-        effect->e.e_op.args_nr = argv.size();
+        e_op.args_nr = argv.size();
 
-        std::copy(argv.begin(), argv.end(), effect->e.e_op.args);
+        std::copy(argv.begin(), argv.end(), e_op.args);
+        effect = dumb_ptr<effect_t>::make(e_op, nullptr);
         return true;
     }
 
     static
     dumb_ptr<expr_t> dot_expr(dumb_ptr<expr_t> expr, int id)
     {
-        dumb_ptr<expr_t> retval = magic_new_expr(EXPR::SPELLFIELD);
-        retval->e.e_field.id = id;
-        retval->e.e_field.expr = expr;
+        ExprField e_field;
+        e_field.id = id;
+        e_field.expr = expr;
+        dumb_ptr<expr_t> retval = dumb_ptr<expr_t>::make(e_field);
 
         return retval;
     }
@@ -322,15 +319,16 @@ namespace magic_v2
                         name, fun->signature.size(), argv.size()));
             return false;
         }
-        expr = magic_new_expr(EXPR::FUNAPP);
-        expr->e.e_funapp.line_nr = span.begin.line;
-        expr->e.e_funapp.column = span.begin.column;
-        expr->e.e_funapp.funp = fun;
+        ExprFunApp e_funapp;
+        e_funapp.line_nr = span.begin.line;
+        e_funapp.column = span.begin.column;
+        e_funapp.funp = fun;
 
         assert (argv.size() <= MAX_ARGS);
-        expr->e.e_funapp.args_nr = argv.size();
+        e_funapp.args_nr = argv.size();
 
-        std::copy(argv.begin(), argv.end(), expr->e.e_funapp.args);
+        std::copy(argv.begin(), argv.end(), e_funapp.args);
+        expr = dumb_ptr<expr_t>::make(e_funapp);
         return true;
     }
     static
@@ -407,23 +405,19 @@ namespace magic_v2
         case sexpr::INT:
             {
                 val_t val;
-                val.ty = TYPE::INT;
-                val.v.v_int = x._int;
-                if (val.v.v_int != x._int)
+                val = ValInt{static_cast<int32_t>(x._int)};
+                if (val.get_if<ValInt>()->v_int != x._int)
                     return fail(x, "integer too large"_s);
 
-                out = magic_new_expr(EXPR::VAL);
-                out->e.e_val = val;
+                out = dumb_ptr<expr_t>::make(std::move(val));
                 return true;
             }
         case sexpr::STRING:
             {
                 val_t val;
-                val.ty = TYPE::STRING;
-                val.v.v_string = dumb_string::copys(x._str);
+                val = ValString{dumb_string::copys(x._str)};
 
-                out = magic_new_expr(EXPR::VAL);
-                out->e.e_val = val;
+                out = dumb_ptr<expr_t>::make(std::move(val));
                 return true;
             }
         case sexpr::TOKEN:
@@ -439,25 +433,25 @@ namespace magic_v2
                 if (it != end)
                 {
                     val_t val;
-                    val.ty = TYPE::DIR;
-                    val.v.v_dir = static_cast<DIR>(it - begin);
+                    val = ValDir{static_cast<DIR>(it - begin)};
 
-                    out = magic_new_expr(EXPR::VAL);
-                    out->e.e_val = val;
+                    out = dumb_ptr<expr_t>::make(std::move(val));
                     return true;
                 }
             }
             {
-                if (val_t *val = find_constant(x._str))
+                if (const val_t *val = find_constant(x._str))
                 {
-                    out = magic_new_expr(EXPR::VAL);
-                    out->e.e_val = *val;
+                    val_t copy;
+                    magic_copy_var(&copy, val);
+                    out = dumb_ptr<expr_t>::make(std::move(copy));
                     return true;
                 }
                 else
                 {
-                    out = magic_new_expr(EXPR::ID);
-                    out->e.e_id = intern_id(x._str);
+                    ExprId e;
+                    e.e_id = intern_id(x._str);
+                    out = dumb_ptr<expr_t>::make(e);
                     return true;
                 }
             }
@@ -475,9 +469,7 @@ namespace magic_v2
                     e_location_t loc;
                     if (!parse_loc(x, loc))
                         return false;
-                    out = magic_new_expr(EXPR::AREA);
-                    out->e.e_area.ty = AREA::LOCATION;
-                    out->e.e_area.a.a_loc = loc;
+                    out = dumb_ptr<expr_t>::make(loc);
                     return true;
                 }
                 if (op == "@+"_s)
@@ -491,11 +483,11 @@ namespace magic_v2
                         return false;
                     if (!parse_expression(x._list[3], height))
                         return false;
-                    out = magic_new_expr(EXPR::AREA);
-                    out->e.e_area.ty = AREA::RECT;
-                    out->e.e_area.a.a_rect.loc = loc;
-                    out->e.e_area.a.a_rect.width = width;
-                    out->e.e_area.a.a_rect.height = height;
+                    ExprAreaRect a_rect;
+                    a_rect.loc = loc;
+                    a_rect.width = width;
+                    a_rect.height = height;
+                    out = dumb_ptr<expr_t>::make(a_rect);
                     return true;
                 }
                 if (op == "TOWARDS"_s)
@@ -512,12 +504,12 @@ namespace magic_v2
                         return false;
                     if (!parse_expression(x._list[4], depth))
                         return false;
-                    out = magic_new_expr(EXPR::AREA);
-                    out->e.e_area.ty = AREA::BAR;
-                    out->e.e_area.a.a_bar.loc = loc;
-                    out->e.e_area.a.a_bar.dir = dir;
-                    out->e.e_area.a.a_bar.width = width;
-                    out->e.e_area.a.a_bar.depth = depth;
+                    ExprAreaBar a_bar;
+                    a_bar.loc = loc;
+                    a_bar.dir = dir;
+                    a_bar.width = width;
+                    a_bar.depth = depth;
+                    out = dumb_ptr<expr_t>::make(a_bar);
                     return true;
                 }
                 if (op == "."_s)
@@ -630,10 +622,10 @@ namespace magic_v2
                 dumb_ptr<spellguard_t> alt;
                 if (!parse_spellguard(*begin, alt))
                     return false;
-                dumb_ptr<spellguard_t> choice = new_spellguard(SPELLGUARD::CHOICE);
-                choice->next = out;
-                choice->s.s_alt = alt;
-                out = choice;
+                GuardChoice choice;
+                auto next = out;
+                choice.s_alt = alt;
+                out = dumb_ptr<spellguard_t>::make(choice, next);
             }
             return true;
         }
@@ -666,8 +658,9 @@ namespace magic_v2
             dumb_ptr<expr_t> condition;
             if (!parse_expression(s._list[1], condition))
                 return false;
-            out = new_spellguard(SPELLGUARD::CONDITION);
-            out->s.s_condition = condition;
+            GuardCondition cond;
+            cond.s_condition = condition;
+            out = dumb_ptr<spellguard_t>::make(cond, nullptr);
             return true;
         }
         if (cmd == "MANA"_s)
@@ -677,8 +670,9 @@ namespace magic_v2
             dumb_ptr<expr_t> mana;
             if (!parse_expression(s._list[1], mana))
                 return false;
-            out = new_spellguard(SPELLGUARD::MANA);
-            out->s.s_mana = mana;
+            GuardMana sp;
+            sp.s_mana = mana;
+            out = dumb_ptr<spellguard_t>::make(sp, nullptr);
             return true;
         }
         if (cmd == "CASTTIME"_s)
@@ -688,8 +682,9 @@ namespace magic_v2
             dumb_ptr<expr_t> casttime;
             if (!parse_expression(s._list[1], casttime))
                 return false;
-            out = new_spellguard(SPELLGUARD::CASTTIME);
-            out->s.s_casttime = casttime;
+            GuardCastTime ct;
+            ct.s_casttime = casttime;
+            out = dumb_ptr<spellguard_t>::make(ct, nullptr);
             return true;
         }
         if (cmd == "CATALYSTS"_s)
@@ -703,8 +698,9 @@ namespace magic_v2
                     return false;
                 magic_add_component(&items, id, count);
             }
-            out = new_spellguard(SPELLGUARD::CATALYSTS);
-            out->s.s_catalysts = items;
+            GuardCatalysts cat;
+            cat.s_catalysts = items;
+            out = dumb_ptr<spellguard_t>::make(cat, nullptr);
             return true;
         }
         if (cmd == "COMPONENTS"_s)
@@ -718,8 +714,9 @@ namespace magic_v2
                     return false;
                 magic_add_component(&items, id, count);
             }
-            out = new_spellguard(SPELLGUARD::COMPONENTS);
-            out->s.s_components = items;
+            GuardComponents comp;
+            comp.s_components = items;
+            out = dumb_ptr<spellguard_t>::make(comp, nullptr);
             return true;
         }
         return fail(s._list[0], "unknown guard"_s);
@@ -731,7 +728,7 @@ namespace magic_v2
     {
         // these backward lists could be forward by keeping the reference
         // I know this is true because Linus said so
-        out = new_effect(EFFECT::SKIP);
+        out = dumb_ptr<effect_t>::make(EffectSkip{}, nullptr);
         while (end != begin)
         {
             const SExpr& s = *--end;
@@ -772,9 +769,10 @@ namespace magic_v2
             if (!parse_expression(s._list[2], expr))
                 return false;
 
-            out = new_effect(EFFECT::ASSIGN);
-            out->e.e_assign.id = intern_id(name);
-            out->e.e_assign.expr = expr;
+            EffectAssign e_assign;
+            e_assign.id = intern_id(name);
+            e_assign.expr = expr;
+            out = dumb_ptr<effect_t>::make(e_assign, nullptr);
             return true;
         }
         if (cmd == "SCRIPT"_s)
@@ -787,36 +785,37 @@ namespace magic_v2
             std::unique_ptr<const ScriptBuffer> script = parse_script(body, s._list[1]._span.begin.line, true);
             if (!script)
                 return fail(s._list[1], "script does not compile"_s);
-            out = new_effect(EFFECT::SCRIPT);
-            out->e.e_script = dumb_ptr<const ScriptBuffer>(script.release());
+            EffectScript e;
+            e.e_script = dumb_ptr<const ScriptBuffer>(script.release());
+            out = dumb_ptr<effect_t>::make(e, nullptr);
             return true;
         }
         if (cmd == "SKIP"_s)
         {
             if (s._list.size() != 1)
                 return fail(s, "not 0 arg"_s);
-            out = new_effect(EFFECT::SKIP);
+            out = dumb_ptr<effect_t>::make(EffectSkip{}, nullptr);
             return true;
         }
         if (cmd == "ABORT"_s)
         {
             if (s._list.size() != 1)
                 return fail(s, "not 0 arg"_s);
-            out = new_effect(EFFECT::ABORT);
+            out = dumb_ptr<effect_t>::make(EffectAbort{}, nullptr);
             return true;
         }
         if (cmd == "END"_s)
         {
             if (s._list.size() != 1)
                 return fail(s, "not 0 arg"_s);
-            out = new_effect(EFFECT::END);
+            out = dumb_ptr<effect_t>::make(EffectEnd{}, nullptr);
             return true;
         }
         if (cmd == "BREAK"_s)
         {
             if (s._list.size() != 1)
                 return fail(s, "not 0 arg"_s);
-            out = new_effect(EFFECT::BREAK);
+            out = dumb_ptr<effect_t>::make(EffectBreak{}, nullptr);
             return true;
         }
         if (cmd == "FOREACH"_s)
@@ -850,11 +849,13 @@ namespace magic_v2
                 return false;
             if (!parse_effect(s._list[4], effect))
                 return false;
-            out = new_effect(EFFECT::FOREACH);
-            out->e.e_foreach.id = intern_id(var);
-            out->e.e_foreach.area = area;
-            out->e.e_foreach.body = effect;
-            out->e.e_foreach.filter = filter;
+
+            EffectForEach e_foreach;
+            e_foreach.id = intern_id(var);
+            e_foreach.area = area;
+            e_foreach.body = effect;
+            e_foreach.filter = filter;
+            out = dumb_ptr<effect_t>::make(e_foreach, nullptr);
             return true;
         }
         if (cmd == "FOR"_s)
@@ -873,11 +874,13 @@ namespace magic_v2
                 return false;
             if (!parse_effect(s._list[4], effect))
                 return false;
-            out = new_effect(EFFECT::FOR);
-            out->e.e_for.id = intern_id(var);
-            out->e.e_for.start = low;
-            out->e.e_for.stop = high;
-            out->e.e_for.body = effect;
+
+            EffectFor e_for;
+            e_for.id = intern_id(var);
+            e_for.start = low;
+            e_for.stop = high;
+            e_for.body = effect;
+            out = dumb_ptr<effect_t>::make(e_for, nullptr);
             return true;
         }
         if (cmd == "IF"_s)
@@ -897,11 +900,13 @@ namespace magic_v2
                     return false;
             }
             else
-                if_false = new_effect(EFFECT::SKIP);
-            out = new_effect(EFFECT::IF);
-            out->e.e_if.cond = cond;
-            out->e.e_if.true_branch = if_true;
-            out->e.e_if.false_branch = if_false;
+                if_false = dumb_ptr<effect_t>::make(EffectSkip{}, nullptr);
+
+            EffectIf e_if;
+            e_if.cond = cond;
+            e_if.true_branch = if_true;
+            e_if.false_branch = if_false;
+            out = dumb_ptr<effect_t>::make(e_if, nullptr);
             return true;
         }
         if (cmd == "WAIT"_s)
@@ -911,8 +916,9 @@ namespace magic_v2
             dumb_ptr<expr_t> expr;
             if (!parse_expression(s._list[1], expr))
                 return false;
-            out = new_effect(EFFECT::SLEEP);
-            out->e.e_sleep = expr;
+            EffectSleep e;
+            e.e_sleep = expr;
+            out = dumb_ptr<effect_t>::make(e, nullptr);
             return true;
         }
         if (cmd == "CALL"_s)
@@ -981,9 +987,9 @@ namespace magic_v2
                 if (!parse_spellbody(*begin, alt))
                     return false;
                 auto tmp = out;
-                out = new_spellguard(SPELLGUARD::CHOICE);
-                out->next = tmp;
-                out->s.s_alt = alt;
+                GuardChoice choice;
+                choice.s_alt = alt;
+                out = dumb_ptr<spellguard_t>::make(choice, tmp);
             }
             return true;
         }
@@ -1031,10 +1037,11 @@ namespace magic_v2
             }
             if (!build_effect_list(begin, end, effect))
                 return false;
-            out = new_spellguard(SPELLGUARD::EFFECT);
-            out->s.s_effect.effect = effect;
-            out->s.s_effect.at_trigger = attrig;
-            out->s.s_effect.at_end = atend;
+            effect_set_t s_effect;
+            s_effect.effect = effect;
+            s_effect.at_trigger = attrig;
+            s_effect.at_end = atend;
+            out = dumb_ptr<spellguard_t>::make(s_effect, nullptr);
             return true;
         }
         return fail(s._list[0], "unknown spellbody"_s);
@@ -1068,7 +1075,7 @@ namespace magic_v2
             return false;
         val_t tmp;
         magic_eval(dumb_ptr<env_t>(&magic_default_env), &tmp, expr);
-        return bind_constant(span, name, &tmp);
+        return bind_constant(span, name, std::move(tmp));
     }
     static
     bool parse_anchor(io::LineSpan span, const std::vector<SExpr>& in)
diff --git a/src/map/magic.cpp b/src/map/magic.cpp
index bc46f86..a0238b5 100644
--- a/src/map/magic.cpp
+++ b/src/map/magic.cpp
@@ -42,8 +42,6 @@ namespace tmwa
 {
 namespace magic
 {
-#undef DEBUG
-
 /// Return a pair of strings, {spellname, parameter}
 /// Parameter may be empty.
 static
@@ -97,18 +95,13 @@ int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation)
         int near_miss;
         dumb_ptr<env_t> env =
             spell_create_env(&magic_conf, spell, caster, power, parameter);
-        effect_set_t *effects;
+        const effect_set_t *effects;
 
         if (bool(spell->flags & SPELL_FLAG::NONMAGIC) || (power >= 1))
             effects = spell_trigger(spell, caster, env, &near_miss);
         else
             effects = nullptr;
 
-#ifdef DEBUG
-        FPRINTF(stderr, "Found spell `%s', triggered = %d\n"_fmt, spell_,
-                effects != nullptr);
-#endif
-
         MAP_LOG_PC(caster, "CAST %s %s"_fmt,
                 spell->name, effects ? "SUCCESS"_s : "FAILURE"_s);
 
-- 
cgit v1.2.3-70-g09d2