Yep — what you’re describing is *exactly* how fast JS engines make “normal-looking arrays” fast: **an array carries an internal “elements kind”**, e.g. “all int32”, “all doubles”, “boxed values”, and it *transitions* to a more general representation the moment you store something that doesn’t fit. V8 literally documents this “elements kinds” + transitions model (packed smi → packed double → packed elements, plus “holey” variants). ([V8][1])

### 1) “Numbers-only until polluted” arrays: do it — but keep it brutally simple

For CellScript, you can get most of the win with just **three** internal kinds:

* **PACKED_I32** (or “fit int” if you want)
* **PACKED_F64**
* **PACKED_VALUE** (boxed JSValue)

Rules:

* On write, if kind is I32 and value is i32 → store unboxed.
* If value is non-i32 number → upgrade whole backing store to F64.
* If value is non-number → upgrade to VALUE.
* Never downgrade (keep it one-way like V8 does). ([V8][1])

Extra credit that matters more than it sounds: **forbid sparse arrays** (or define them away). If an out-of-range write extends the array, *fill with null* so the storage remains dense. That keeps iteration tight and avoids “holey” variants (which are a real perf cliff in engines). ([V8][1])
Your standard library already nudges toward dense arrays (constructors like `array(n)` fill with null). 

### 2) Your “fast property op assuming data properties only” is the biggest bang

Since you’ve banned Proxy and all descriptor/accessor machinery , you can add VM ops that assume the world is sane:

* `GET_PROP_PLAIN`
* `SET_PROP_PLAIN`

Then slap an **inline cache** (IC) on them: cache `(shape_id, slot_offset)` for a given property name/key. On hit → direct load/store by offset. On miss → slow path resolves, updates cache.

This is not hypothetical: QuickJS forks have pursued this; QuickJS-NG had discussion of polymorphic inline caches (PolyIC) with reported big wins in some forks. ([GitHub][2])

Even if you keep “objects are fully dynamic”, ICs still work great because most call sites are monomorphic in practice.

### 3) “What else should I remove to make JITing easier + faster?”

The best deletions are the ones that eliminate **invalidation** (stuff that forces “anything could change”):

1. **Prototype mutation** (you already forbid it; `meme` creates, “prototypes cannot be changed”). 
2. **Accessors / defineProperty / descriptors** (you already forbid it). 
3. **Proxy / Reflect** (already gone). 
4. **Property enumeration order guarantees** — and you already *don’t* guarantee key order for `array(object)`. 
   That’s secretly huge: you can store properties in whatever layout is fastest (hash + compact slots) without “insertion order” bookkeeping.
5. **Sparse arrays / hole semantics** (if you delete this, your array JIT story becomes *way* easier).

Stuff that’s *less* important than people think:

* Keeping `delete` as a keyword is fine *if* you implement it in a JIT-friendly way (next point).

### 4) You can keep `delete` without wrecking shapes: make it a “logical delete”

If you want “`obj[k] = null` deletes it”, you can implement deletion as:

* keep the slot/offset **stable**
* store **null** and mark the property as “absent for enumeration / membership”

So the shape doesn’t thrash and cached offsets stay valid. `delete obj[k]` becomes the same thing.

That’s the trick: you keep the *semantics* of deletion, but avoid the worst-case performance behavior (shape churn) that makes JITs sad.

### 5) What assumptions do “meme + immutable prototypes” unlock?

Two big ones:

* **Prototype chain links never change**, so once you’ve specialized a load, you don’t need “prototype changed” invalidation machinery. 
* If your prototypes are usually **stone** (module exports from `use()` are stone) , then prototype *contents* don’t change either. That means caching “property X lives on prototype P at offset Y” is stable forever.

In a JIT or even in an interpreter with ICs, you can:

* guard receiver shape once per loop (or hoist it)
* do direct loads either from receiver or a known prototype object

### 6) What do `stone` and `def` buy you, concretely?

**stone(value)** is a promise: “no more mutations, deep.” 
That unlocks:

* hoisting shape checks out of loops (because the receiver won’t change shape mid-loop)
* for stone arrays: no push/pop → stable length + stable element kind
* for stone objects: stable slot layout; you can treat them like read-only structs *when the key is known*

But: **stone doesn’t magically remove the need to identify which layout you’re looking at.** If the receiver is not a compile-time constant, you still need *some* guard (shape id or pointer class id). The win is you can often make that guard **once**, then blast through the loop.

**def** is about *bindings*, not object mutability:

* a `def` global / module binding can be constant-folded and inlined
* a `def` that holds a `key()` capability  makes `obj[that_key]` an excellent JIT target: the key identity is constant, so the lookup can be cached very aggressively.

### 7) LuaJIT comparison: what it’s doing, and where you could beat it

LuaJIT is fast largely because it’s a **tracing JIT**: it records a hot path, emits IR, and inserts **guards** that bail out if assumptions break. ([GitHub][3])
Tables also have a split **array part + hash part** representation, which is why “array-ish” use is fast. ([Percona Community][4])

Could CellScript beat LuaJIT? Not as an interpreter. But with:

* unboxed dense arrays (like above),
* plain-data-property ICs,
* immutable prototypes,
* plus either a trace JIT or a simple baseline JIT…

…you can absolutely be in “LuaJIT-ish” territory for the patterns you care about (actors + data + tight array loops). The big JS engines are still monsters in general-purpose optimization, but your *constraints* are real leverage if you cash them in at the VM/JIT level.

If you implement only two performance features this year: **(1) dense unboxed arrays with one-way kind transitions, and (2) inline-cached GET/SET for plain properties.** Everything else is garnish.

[1]: https://v8.dev/blog/elements-kinds?utm_source=chatgpt.com "Elements kinds in V8"
[2]: https://github.com/quickjs-ng/quickjs/issues/116?utm_source=chatgpt.com "Optimization: Add support for Poly IC · Issue #116 · quickjs- ..."
[3]: https://github.com/tarantool/tarantool/wiki/LuaJIT-SSA-IR?utm_source=chatgpt.com "LuaJIT SSA IR"
[4]: https://percona.community/blog/2020/04/29/the-anatomy-of-luajit-tables-and-whats-special-about-them/?utm_source=chatgpt.com "The Anatomy of LuaJIT Tables and What's Special About Them"
