Steps GitHub issue

Overview GitHub issue

vibecode
{"vibecode": {
    "section": "overview",
    "method": "%utils.steps",
    "purpose": "count_caspian_level_steps_for_a_block",
    "unit": "one_step_per_eval_or_exec_stmt_call",
    "default": "off_zero_overhead_unless_call_is_active",
    "nesting": "each_call_gets_accurate_count_of_its_own_scope"
}}

%utils.steps wraps a block and counts every Caspian-level evaluation step inside it, including all nested function calls. It is off by default — zero overhead unless a %utils.steps call is active.

The step unit is one call to eval or exec_stmt in the interpreter. This is deterministic (same program always gives the same count), engine-independent (any compliant engine implements the same unit), and meaningful at the language level rather than the hardware level.


Syntax GitHub issue

vibecode
{"vibecode": {
    "section": "syntax",
    "form": "%utils.steps do ... end",
    "returns": "result_object",
    "result_methods": ["steps", "value"]
}}
$result = %utils.steps do
    &some_routine
end

$result.steps    # total steps executed inside the block (including nested calls)
$result.value    # return value of the block

The %utils.steps call returns a result object with two fields:

Field Type Description
steps Number Total Caspian-level steps executed inside the block
value Any The return value of the last statement in the block

Nested Steps GitHub issue

vibecode
{"vibecode": {
    "section": "nested_steps",
    "behavior": "each_call_counts_its_own_scope_independently",
    "mechanism": "interpreter_maintains_stack_of_active_step_counters",
    "note": "inner_steps_count_toward_both_inner_and_outer_totals"
}}

%utils.steps calls can be nested. Each call maintains its own counter. Inner steps count toward both the inner and outer totals, giving each call an accurate picture of its own scope:

$outer = %utils.steps do
    &foo

    $inner = %utils.steps do
        &bar
    end

    &gup
end

$inner.steps    # steps for &bar only
$outer.steps    # steps for &foo + &bar + &gup combined

The interpreter maintains a stack of active step counters. Every eval and exec_stmt call increments all counters currently on the stack.


Counting Rules GitHub issue

vibecode
{"vibecode": {
    "section": "counting_rules",
    "unit": "one_step_per_eval_or_exec_stmt_call",
    "includes": ["nested_function_calls", "method_calls", "operator_evaluations",
        "block_bodies"],
    "deterministic": true,
    "hardware_independent": true
}}

One step is counted for each call to eval or exec_stmt, including:

The count is deterministic — the same program with the same input always produces the same step count. It is not a wall-clock measurement and is not affected by CPU load, caching, or context switching.


Implementation Note GitHub issue

vibecode
{"vibecode": {
    "section": "implementation_note",
    "lua_reference": "counter_stack_in_interpreter_incremented_at_eval_and_exec_stmt",
    "overhead": "one_integer_increment_per_step_when_active",
    "compliant_engine_requirement": "must_implement_same_step_unit_and_nesting_semantics"
}}

In the Lua reference implementation, the interpreter maintains a stack of integer counters. When a %utils.steps call is entered, a new counter is pushed. Every eval and exec_stmt call increments all counters on the stack. When the call's block exits, the top counter is popped and wrapped in the result object.

When no %utils.steps call is active the stack is empty and no incrementing occurs — zero overhead.

A compliant engine must implement the same step unit and nesting semantics so that step counts are comparable across engines.


Technical details GitHub issue

vibecode
{"vibecode": {
    "section": "technical_details",
    "covers": ["purpose", "calculation", "engine_independence", "where_steps_surface"],
    "unit": "one_step_per_eval_or_exec_stmt_call",
    "deterministic": true,
    "engine_independent": true
}}

A step is a single unit of work at the Caspian language level. Steps are how Caspian programs and tooling answer the question "how much work did this process do?". They provide a simple way to compare the efficiency of different algorithms — run the processes and compare the step counts.

Purpose GitHub issue

Steps exist as a deterministic, engine-independent measurement of Caspian-level work. The same program with the same input always produces the same step count, regardless of which engine ran it or which machine it ran on.

This makes steps the right tool for several jobs that wall-clock time handles poorly:

Steps are not a wall-clock substitute. They count interpreter advances; they don't count seconds spent in host I/O, sleeping, blocking on the network, or anything else outside the Caspian interpreter loop.

How they're calculated GitHub issue

One step is counted for each call to eval or exec_stmt in the engine — the two interpreter primitives that advance a Caspian program by one observable language-level step. This includes:

It does NOT include:

The count is monotonic — it only goes up over the life of a counter. It's stable across runs — same program, same input, same number. And it's hardware-independent — CPU speed, cache behavior, and system load don't affect it.

Engine independence GitHub issue

Steps are a Caspian language-level concept, not a Lua concept. The unit is defined at the language layer: one call to eval or exec_stmt is one step. Any compliant engine — the Lua reference implementation today, hypothetical future implementations in other host languages tomorrow — must produce identical step counts for the same Caspian program with the same input.

This is what makes steps useful for cross-engine comparison: if engine A and engine B produce different step counts for the same program, one of them is non-compliant. Step counts are portable: the same number means the same amount of work on every engine.

The Lua reference implementation happens to be the first place steps are implemented; see the Implementation Note section above for the specific Lua-side mechanism. That's an implementation detail. The language-level semantics — what counts as a step, when it's incremented, what stays stable — are spec-level and bind every engine equally.

Where steps surface GitHub issue

Two places in the language and runtime today:

Both produce the same unit. A region measured with %utils.steps is directly comparable to the process-level count from the manifest, because there's only one definition of "a step" in the language.

© 2026 Puck.uno