> **RESOLVED** (NL.2 attempt 3). Root cause: the numeric-limit accessor
> intercept treated ANY receiver whose text matched a builtin numeric type
> name as a TYPE receiver, without first checking whether that identifier
> resolved to an in-scope VALUE binding. An F0.6 backtick raw identifier
> (`` `f64 := … ``) binds a local under the stripped name `f64`; field access
> on it (`` `f64.epsilon ``) parses as an `.identifier` receiver, which the
> intercept silently folded to the type's numeric limit — a silent-wrong-value
> bug.
>
> Fix (value-binding precedence for `.identifier` receivers; `.type_expr`
> receivers are unambiguous types and never shadowed):
> - `src/ir/lower.zig` — `lowerNumericLimit`: after confirming the receiver is
>   a builtin numeric type name and the field is a limit accessor, return null
>   (defer to ordinary field lowering) when `fa.object` is an `.identifier`
>   that `Scope.lookup` resolves to a value binding.
> - `src/ir/expr_typer.zig` — numeric-limit inference arm: mirror the same
>   guard so inferred types match lowering (avoids the issue-0083 two-resolver
>   desync).
>
> Bare `f64.epsilon` / `i32.max` (no shadowing binding) still fold — the parser
> classifies a bare builtin name as a `.type_expr` (parser.zig:2743), so the
> bare receiver is never value-shadowed even in a scope where `` `f64 `` is
> bound. Float-only-on-int and non-numeric-receiver errors are unchanged.
>
> Regression: `examples/0161-types-numeric-limit-value-shadow.sx` (raw
> `` `f64 ``/`` `i32 ``/`` `u8 `` value reads coexisting with bare folds) +
> unit test in `src/ir/expr_typer.test.zig`. NL.1 (`examples/0148`) / NL.2
> (`examples/0159`, `examples/0160`) unregressed.

# 0092 — numeric-limit intercept hijacks raw reserved-spelled value receivers

## Symptom

Field access on a raw reserved-spelled value binding is interpreted as a builtin
type numeric-limit access instead of an ordinary value field access. Observed:
the repro prints `0.000000 2147483647` (`f64.epsilon` / `i32.max`). Expected:
it prints `12 78` from the `Box` fields.

## Reproduction

```sx
#import "modules/std.sx";

Box :: struct { epsilon: i64; max: i64; }

main :: () -> i32 {
    `f64 := Box.{ epsilon = 12, max = 34 };
    `i32 := Box.{ epsilon = 56, max = 78 };
    print("{} {}\n", `f64.epsilon, `i32.max);
    return 0;
}
```

## Investigation prompt

Investigate issue 0092: raw reserved-spelled value receivers are being captured
by the numeric-limit accessor intercept. In `src/ir/lower.zig`, start at
`Lowering.lowerFieldAccess` and `Lowering.lowerNumericLimit` (currently around
`lower.zig:4826` / `lower.zig:4923`). The intercept treats any
`.identifier`/`.type_expr` receiver whose text is a builtin type name as a type
receiver, without first checking whether the identifier resolves to a value
binding in the current lexical scope. Mirror the fix in `src/ir/expr_typer.zig`
around the numeric-limit inference arm so inferred types match lowering.

Likely fix: for `.identifier` receivers, prefer an in-scope value binding
(`Scope.lookup`) over the builtin-type numeric-limit intercept; keep the
intercept for actual type receivers (`.type_expr`, and bare reserved integer
names with no value binding). If raw provenance is available in the AST, using it
to disambiguate is also acceptable, but the observable rule must be that
`` `f64.epsilon `` reads the value field when `` `f64 `` is a value binding,
while bare `f64.epsilon` / `i32.max` still fold as numeric limits.

Verification: pin a regression test from the repro above. It should print
`12 78`. Also verify the existing numeric-limit examples still pass:
`examples/0148-types-int-numeric-limits.sx`,
`examples/0159-types-float-numeric-limits.sx`, and the negative diagnostics in
`examples/0160-types-float-numeric-limits-errors.sx`.
