<!-- {% raw %} — Jekyll/Liquid guard: this doc contains Go composite-literal and template syntax ({{ … }}) that Liquid would otherwise parse; the HTML comment hides the tag on GitHub. -->
# Conversion Strategies — Technical Reference

> **📖 This is the exhaustive technical reference.** For a shorter, example-driven overview of how
> each Go construct maps to C#, start with **[`ConversionStrategies.md`](ConversionStrategies.md)** —
> every section there links back here for the full detail. Read this document when you need the
> *why*: the exact emitted form, the edge cases, the Phase-3 fixes, the behavioral-test guards, and
> the C#-vs-Go semantic reasoning behind a decision. It is the authoritative record; the summary is
> the front door.

> **Updated 2026-06-27 for the "go2cs2" generation of the converter.** This is a living
> document; as more use cases are converted these strategies are refined. The current converter
> is written in **Go** (using the official `go/ast` + `go/types` toolchain, under `src/go2cs/`)
> and emits C# that leans on two things the visible code does not show in full: a hand-written
> runtime library, **`golib`** (`src/core/golib/`), and a set of **[Roslyn](Glossary.md#roslyn) source generators**
> (`src/gen/go2cs-gen/`) that synthesize the Go semantics which cannot be written directly in C#.
> Notes that previously referenced the retired ANTLR4/C# converter or the old `gocore` library
> have been updated to reflect this. See also: [`Architecture.md`](Architecture.md),
> [`Glossary.md`](Glossary.md), [`Roadmap.md`](Roadmap.md), and [`CLAUDE.md`](../CLAUDE.md).

The guiding goal: the generated C# should be both *behaviorally* and *visually* similar to the
original Go, so that a Go developer can read the output and follow it. The runtime library and
the generators exist to keep the visible converted code close to the Go original.

> **How this reference is organized.** Each `##` topic opens with the high-level rule (the same
> ground the summary covers) and is then followed by `###` subsections documenting specific
> conversion decisions, edge cases, and fixes — most keyed to the [behavioral test](Glossary.md#guard) that guards them.
> When updating the converter, add the deep detail here and the reader-facing example to the
> summary (see [`../CLAUDE.md`](../CLAUDE.md), "Record the conversion decision").

## Topics

* [Package Conversion](#package-conversion)
* [Package-Level Variable Initialization Order](#package-level-variable-initialization-order)
* [Compiled Library versus Source Code](#compiled-library-versus-source-code)
* [Constant Values](#constant-values)
* [Native and Narrow Integer Types](#native-and-narrow-integer-types)
* [Named Numeric Types and Constant Contexts](#named-numeric-types-and-constant-contexts)
* [Floating-Point Formatting](#floating-point-formatting)
* [Nil and Zero Values](#nil-and-zero-values)
* [Empty Interface (`any`)](#empty-interface-any)
* [Multi-Assignment and Evaluation Order](#multi-assignment-and-evaluation-order)
* [Short Variable Redeclaration (Shadowing)](#short-variable-redeclaration-shadowing)
* [Multi-Result Values and Comma-Ok Forms](#multi-result-values-and-comma-ok-forms)
* [Slices and Arrays](#slices-and-arrays)
* [Strings (`@string` and `sstring`)](#strings-string-and-sstring)
* [Maps and Channels](#maps-and-channels)
* [Generic Constraints](#generic-constraints)
* [Type Aliasing](#type-aliasing)
* [Delegates to Value Receiver Instances](#delegates-to-value-receiver-instances)
* [Defer / Panic / Recover](#defer--panic--recover)
* [Expression Switch Statements](#expression-switch-statements)
* [Type Switch Statements](#type-switch-statements)
* [Struct Types](#struct-types)
* [Struct Type Embedding](#struct-type-embedding)
* [Interfaces](#interfaces)
* [Pointers](#pointers)
* [Implicit Pointer Dereferencing](#implicit-pointer-dereferencing)
* [Labeled Control Flow and Loop Variables](#labeled-control-flow-and-loop-variables)
* [The `go.golib` support namespace](#the-gogolib-support-namespace)
* [Source Generators](#source-generators)
* [Manually-Converted Declarations](#manually-converted-declarations)
* [Comments](#comments)
* [Deterministic Output](#deterministic-output)

## Package Conversion
Although a Go package more traditionally parallels a C# namespace, Go includes referenceable functions directly from within a package root, for example, the `Println` function in the `fmt` package is called like: `fmt.Println("Hello, world")`. For C#, only type declarations, e.g., `class`, `struct`, `enum`, etc., are allowed in a namespace; functions exist as part of a `class` or `struct`. Described from a C# perspective, all Go functions are [`static`](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members), i.e., the functions exist separately from an instance of a type. Go supports the notion of a receiver function which allows a function to be targeted to an instance of a type (paralleling the operation of a C# extension function), but this is still a static function.

As such, the conversion strategy for a Go package is to convert it into a static C# partial class, e.g.: `public static partial class fmt_package`. Using a partial class allows all functions within separate files to be available with a single import, e.g.: `using fmt = go.fmt_package;`. The receiver functions are emitted as extension methods on that partial class (decorated with `[GoRecv]`, see [Source Generators](#source-generators)).

So that Go packages are more readily usable in C# applications, all converted code is in a root `go` namespace. Package paths are simply converted to namespaces, so a Go import like `import "unicode/utf8"` becomes a C# using like `using utf8 = go.unicode.utf8_package;`. Each package also emits a `package_info.cs` carrying a `[GoPackage]` assembly attribute plus the package-wide global `using` aliases (Go's built-in types, exported type aliases, etc.).

A consequence of converting a Go method to a C# **extension method** is that C# only discovers an extension method when its containing static class's *namespace* is in scope (via a `using <namespace>;` directive or the enclosing namespace) — a class **alias** such as `using atomic = go.@internal.runtime.atomic_package;` resolves the *type* (`atomic.Uint32`) but does **not** bring the class's extension methods into scope. This matters when a file calls a method on a value whose type comes from a multi-segment-path package (one that lands in a sub-namespace, e.g. `internal/runtime/atomic` → `go.@internal.runtime`): Go never requires importing a value's package merely to call a method on it, so such a file may emit no import — and hence no `using @internal.runtime;` — leaving the extension method invisible and the call mis-binding to a wrong (e.g. embedding-promoted) overload (CS1929). The converter therefore registers the namespace of **every cross-package method's defining package** as a file-local `using` at the call site, independent of the file's explicit imports. (Packages in the root `go` namespace — most top-level stdlib packages — need nothing extra, since same-namespace extension methods are always visible. This is a stdlib-structural concern that only surfaces under multi-segment package paths, so it is guarded by the Phase-3 `runtime` build rather than a single-package behavioral test.)

Go projects that contain a `main` function are converted into a standard C# executable project, i.e., `<OutputType>Exe</OutputType>`. The conversion process can reference and convert needed external projects as library projects, i.e., `<OutputType>Library</OutputType>`, per any encountered `import` statements. In this manner an executable with packages compiled as project-referenced assemblies can be created. To create a single executable, like the original Go counterpart, a [self-contained executable](https://docs.microsoft.com/en-us/dotnet/core/deploying/#publish-self-contained) can be produced.

An executable's **`<AssemblyName>` is the last element of its import path**, mirroring `go build`, which names a binary after the module/directory's final segment — so `module example.com/colordemo` produces `colordemo.exe`, not `example.com.colordemo.exe` (the full dotted project name). Only the `Exe` assembly name is shortened; the `.csproj` filename keeps the full dotted path (its identity in the solution and in `ProjectReference`s), and **library** assemblies keep the full dotted `<AssemblyName>` — their DLL and NuGet `PackageId` (`go.$(AssemblyName)`) must stay unique across the package graph (e.g. `github.com.fatih.color`).

### A project name is the package's FULL import path — a go-file-free container directory does not truncate it

The dotted project name above is the package's **import path**, every segment of it, joined with `.`
(`getProjectName`, `importOperations.go`). That is not a formatting preference: the name is the `.csproj`
filename, the library `<AssemblyName>`, the NuGet `PackageId`, and — minus its last segment — the C#
**namespace**. All four have to be unique across the package graph, and the import path is the only thing
about a package that is unique by construction.

For the standard library the path is read straight off `GOROOT/src`. For everything else the converter has
only a directory, so it recovers the import path the way the go command itself finds the main module:
**walk up to the nearest `go.mod`**, then join that module's declared path with the package's path relative
to it. The result is the import path by definition.

The walk used to stop early. Alongside `go.mod` and `main.go` it treated *the first ancestor directory
holding no `.go` files of its own* as a boundary and named the project after the leaf segment alone. Go
modules are made of such pure container directories — `internal/`, a proto grouping like `xds/core/`, the
`datatransfer/` above an `apiv1/`, a service tree's `endpoints/` parent — so the truncation was routine
rather than exotic, and it cost two things:

* **Duplicate project names.** `cloud.google.com/go/bigquery`'s `datatransfer/apiv1` and `storage/apiv1`
  both emitted `apiv1.csproj`. Visual Studio refuses to open a solution containing two projects of the
  same name and reports no reason — silently doing nothing from a file dialog, offering to forget the
  entry from the recent list — so **the entire generated `.slnx` failed to load** ([issue #35]). One
  reported `-recurse` conversion had **175 such projects across 49 colliding names** out of 1,727.
* **A collapsed namespace.** The truncated arm joins its segments with `.` *before* the separator split
  that builds the namespace, so the qualification never reached it: a truncated `internal/errors` landed
  on `go.errors_package` — the converted standard library's own class.

A go-file-free ancestor is therefore a **fallback, never a stop**: the walk records the leaf-relative name
it would have produced and keeps climbing. The fallback applies only when there is genuinely no module
root anywhere above — a GOPATH-style tree, which is the one case it was ever needed for. Because both the
declaring package and every importer derive the name from the same call, the two sides move together.

One consequence worth knowing when re-converting: recovering the full path **renames** every package that
was previously truncated (743 of the 1,727 above), so a re-conversion into an existing output root leaves
the old, wrongly-named `.csproj` files behind as orphans that a solution may still list. Convert into a
fresh output directory, or clear the old one first.

Guarded by `TestProjectNameSurvivesGoFileFreeContainerDir` and
`TestProjectNameFallsBackWhenNoModuleRoot` (the unit invariant, including the fallback arm) and
`TestRecurseGoFileFreeContainerDirsKeepDistinctProjectNames` (the whole `-recurse` conversion: distinct
`.csproj` files, a `.slnx` with no repeated project name, and matching qualification in the importer's
converted code). That last one also exercises `internal` as a **namespace** segment, which only the
recovered path produces — `go.example.com.app.@internal.web.api_package`, keyword-escaped on both the
declaration and reference sides.

### The emitted project PATH is budgeted — the file name compresses, the identity never does

Recovering the full import path made every project name correct, and on a deep dependency tree it made
them long. The import path is then spelled **twice** on disk — once by the directory, which mirrors it,
and once by the file name — so the deepest package of the conversion above emitted a **242-character**
project path before the user's output root even counted. Visual Studio refused to load it, and the same
solution failed all over again ([issue #35], second report).

Three separate limits are in play, and only one of them is negotiable:

| Limit | Value | Liftable? |
|---|---|---|
| Visual Studio's project loader | 260 characters (`MAX_PATH`) | **No.** Measured on a machine with `LongPathsEnabled=1`: a 259-character `.csproj` loads and builds, a 265-character one fails with *"The project file could not be loaded. Could not find a part of the path"* — naming a file that is demonstrably present and that `dotnet build` reads without complaint. |
| A single filename **component** | 255 characters | **No, ever.** No registry key, `\\?\` prefix or manifest touches it (measured: a 265-character *path* writes fine on that same machine, a 256-character *file name* does not). |
| Total path, for long-path-aware tools | 32,767 with the key, 260 without | Yes — `dotnet build` and Visual Studio's own `MSBuild.exe` both handled a 280-character artifact tree. |

So enabling long paths is not an answer, and neither is flattening the tree:
`pkg/<import-path>/<dotted>.csproj` and `pkg/<dotted>/<dotted>.csproj` measure **identically**, because a
separator costs exactly what a dot costs. The name cannot simply be shortened to the leaf either — that
is what collided in the first report, and `.slnx` has no display-name override to separate the file name
from the project name (measured: a `DisplayName` attribute does not help; Visual Studio still reports
*"Project name 'v3' already exists in the '/pkg/' solution folder"*). Spelling the path twice is the
floor unless one of the two spellings is **compressed**.

**The file name is what compresses; identity never does.** `projectFileBaseName` bounds the emitted
project path — `<tree-root>/<import-path>/<name>.csproj` — at **200 characters**, which leaves an output
root of up to 59 characters inside `MAX_PATH`. A name that fits is returned verbatim, which is every
package in the standard library (longest emitted path: 101) and every behavioral test (109), so the
committed corpus is untouched. A name that does not becomes `head~tail.hash8`: a readable head carrying
the module, a readable tail carrying the leaf package, and eight hex characters of SHA-256 over the full
canonical name. The C# namespace, the `<AssemblyName>` and therefore the NuGet `PackageId` all keep the
**full import path** — those are identity, and they stay globally unique and legible in a stack trace.
Replaying the reporter's real 1,724-package conversion: 1,724 distinct names, **11** compressed, longest
emitted path exactly 200.

The compression is a pure function of the canonical name, so it is **set-independent**, and that is the
point. The reference side derives a dependency's file name from the import path alone, knowing nothing
about the rest of the conversion. A shortest-unique-*suffix* scheme would have been shorter still, but
under it adding one dependency can rename unrelated projects — the same coupling the first report's fix
removed, one level up.

**Derived paths are redirected rather than budgeted.** `obj\`, `bin\` and the generator output
`EmitCompilerGeneratedFiles` writes *multiply* the import path instead of merely repeating it, so no name
budget reaches them: on the reporter's tree, 1,570 of 1,725 projects produced a generator path over
`MAX_PATH` and 43 produced a **file name** over the unliftable 255. A `-recurse` conversion therefore
emits a `Directory.Build.props` and `Directory.Build.targets` at the output root that move all three to
`<root>\.artifacts\{obj,bin,gen}\<token>\`, where the token is 12 hex characters of
`[MSBuild]::StableStringHash` over the project's root-relative directory. Two details are load-bearing:

* .NET's built-in `UseArtifactsOutput` was evaluated first and does **not** fit — it lays artifacts out
  under `$(ArtifactsPath)\obj\$(MSBuildProjectName)\`, and the project name *is* the long thing here.
  Measured at a 13-character output root, the deepest package still landed a 309-character artifact.
* `CompilerGeneratedFilesOutputPath` is set from the **`.targets`**, not the `.props` — the generated
  `.csproj` sets it in the project body, which beats any `.props` value — and with **no trailing
  separator**, because csc receives it as `/generatedfilesout:"<path>"` where a trailing backslash escapes
  the closing quote and the compiler is handed the directory as a file name (CS2021). Keeping this out of
  the csproj template is also what holds the standard library and behavioral corpus at zero movement.

Hint names are capped to 128 characters in the analyzer itself (`Common.GetValidFileName`), compressing to
`<head>-<hash16>.g.cs`. A hint name is only a label — Roslyn requires it to be unique within a generator
and nothing else — so the cap has no semantic consequence and the files it names are git-ignored. The
elision separator must come from the same allow-list `IsValidHintNameChar` enforces: a tilde there reaches
`AddSource` unscrubbed and throws, which surfaces as **CS8785, a warning**, leaving the generator to
contribute nothing and the build to fail on the type it should have emitted.

Guarded by `TestProjectFileBaseNameLeavesCorpusNamesAlone`, `…BoundsTheEmittedPath`,
`…BoundaryIsExact`, `…IsDeterministicAndSetIndependent`, `…KeepsDistinctNamesDistinct`, and
`TestIssue35ReplayCorpus` (which replays a real generated `.slnx` when `GO2CS_ISSUE35_CORPUS` points at
one), plus `TestRecurseDeepImportPathStaysInsideMaxPath` — a whole `-recurse` conversion over the
reporter's actual shape, asserting the compressed file name, the untouched `AssemblyName`, the tree-wide
path bound and the reference side's agreement.

[issue #35]: https://github.com/ritchiecarroll/go2cs/issues/35

### The recurse output root records its runtime root: the `$(go2csPath)` default

**A local-references `-recurse` conversion pins the runtime root it resolved against into the output
root's generated `Directory.Build.props`** ([issue #36]):

```xml
<PropertyGroup Condition="'$(go2csPath)' == ''">
  <go2csPath>C:/Users/mason/go2cs-runtime/</go2csPath>
</PropertyGroup>
```

The `-go2cspath` command-line flag is a conversion-time input — it is where the converter reads each
imported package's `package_info.cs` from and what it resolves `$(go2csPath)core/…` references
against — but before this pin its value never reached the emitted build files. The MSBuild property
of the same name then had only the csproj template's fallback (`$(USERPROFILE)/go2cs/`, or
`$(SolutionDir)` under Debug), so converting into an isolated output root produced a solution whose
stdlib/runtime/analyzer references could not resolve unless the runtime happened to live at
`~/go2cs` or a deploy-core `Directory.Build.props` sat above the output. Three details:

* When the output root IS the runtime root (the one-positional form), the pin stays relative —
  `$(MSBuildThisFileDirectory)`, the deploy-core form — so the tree can move as a unit. An isolated
  output root pins the absolute resolved root (forward slashes and a trailing separator, per the
  section below); a re-conversion refreshes it.
* The `Condition` keeps it a default: a `go2csPath` environment variable, a `-p:go2csPath` build
  global, or a higher `Directory.Build.props` still wins.
* `-recurse=nuget` deliberately emits **no** pin — it has no `$(go2csPath)` references to resolve,
  and its props defaults `GoStdLibVersion` instead. A foreign `Directory.Build.props` at the output
  root (deploy-core's, or user-authored) is never clobbered, per the generated-file marker rule.

Guarded by the `go2csPath` assertions in `TestRecurseSyntheticModule` (absolute pin, isolated
output root), `TestRecurseBuildFilesRelativePinWhenRootsCoincide` (relative form), and
`TestRecurseNuGetReferences` (no pin under NuGet references).

[issue #36]: https://github.com/ritchiecarroll/go2cs/issues/36

### Path separators in emitted MSBuild files: forward slashes, on every host

**Every path the converter writes into a `.csproj`, `.slnx`, `.pubxml` or `Directory.Build.props` uses `/`, on every host.** There is no per-host emission and no host-conditional spelling: one converted corpus is correct on Windows, Linux and macOS.

```xml
<ProjectReference Include="$(go2csPath)core/fmt/fmt.csproj" />
<ProjectReference Include="$(go2csPath)gen/go2cs-gen/go2cs-gen.csproj" OutputItemType="Analyzer" … />
<OutDir>bin/$(Configuration)/$(TargetFramework)/</OutDir>
<GoValidationProofFile>$(go2csPath)../docs/validation/$(GoStdLibVersion).$(GoBuildNumber)/fmt.md</GoValidationProofFile>
```

MSBuild accepts `/` in every path context on Windows, and normalizes `\` to `/` on Unix (`FileUtilities.MaybeAdjustFilePath`), so *both* spellings build on both hosts — the .NET SDK's own targets depend on the Unix direction. The reason to pick one is that two other consumers are **not** MSBuild:

1. **The converter's own path arithmetic.** The reference used to be composed by hand — replace every `/` with `\`, then `filepath.Join` a backslash-prefixed file name. On Windows `filepath.Clean` folded that back into a well-formed path; on Unix `filepath.Join` treats `\` as an ordinary filename character, so a Linux-hosted conversion emitted the malformed `$(go2csPath)core\fmt/\fmt.csproj` for *every* stdlib reference in *every* project — silent at emission, a restore failure later (F5, [`PLAN-linux-operation.md`](PLAN-linux-operation.md) §A1.1). The composition is now `emittedProjectReference` (`importOperations.go`): `path.Join` (slash-only, host-independent) over a `filepath.ToSlash`'d directory. `writeProjectFile` and `writeTestProject` additionally `ToSlash` at the emission point, because a sibling reference made relative by `filepath.Rel` arrives OS-native.
2. **The harnesses that READ an emitted reference.** `BehavioralRunner.PreBuildSharedDeps` and `PerformanceRunner` parse `ProjectReference Include="…"` out of the csproj and resolve it with `Path.GetFullPath`, which on Linux does not split on `\` either.

**Consequence to expect when this changes: the sorted reference block can re-order.** References are `sort.Strings`-sorted, and `/` (0x2F) sorts *below* alphanumerics while `\` (0x5C) sorts *above* them, so a pair that differs at a separator boundary swaps. Across the whole 303-project stdlib the flip moved exactly one file's ordering — `net/http`, where `vendor/golang.org/x/net/http2/hpack` had sorted before `vendor/golang.org/x/net/http/httpguts` (`2` < `\`) and now sorts after it (`/` < `2`). Same set, different order; not a content change.

Seven hand-owned `core` files are never re-emitted (`golib`, `testing`, `unsafe`, `internal/godebug`, `internal/concurrent`, `internal/weak`, and `core/Directory.Build.props`), so they carry the form by hand. The one deliberate exception is the shared-project `<Import Project="..\go2cs\go2cs.projitems" Label="Shared" />` in `golib.csproj` and `go2cs-gen.csproj`: that is Visual Studio's own bookkeeping, VS round-trips its exact text, and MSBuild normalizes it on Unix regardless — so it stays backslashed, with a comment saying why.

Guarded by `TestEmbeddedCsprojTemplatesUseForwardSlashesOnly` and `TestValidationPackBlockUsesForwardSlashesOnly` (`csprojTemplate_test.go` — both templates are asserted to contain *no* backslash at all, so a future addition is covered without the guard enumerating it) and by `TestEmittedProjectReferenceIsHostIndependent` / `TestEmittedProjectReferenceForModuleCachePath` (`importOperations_test.go`).

### A GOROOT-vendored reference is named for the package's ON-DISK path

**A standard-library reference takes its project file name from the directory it resolved to, not from the import path as written.** The two are the same string for every package in the standard library except one class — the GOROOT-**vendored** ones, imported as `golang.org/x/…` but existing on disk, and therefore as converted projects, only under `vendor/golang.org/x/…`.

```xml
<!-- crypto/ecdh imports `golang.org/x/crypto/chacha20` -->
<ProjectReference Include="$(go2csPath)core/vendor/golang.org/x/crypto/chacha20/vendor.golang.org.x.crypto.chacha20.csproj" />
```

The directory half was always right — it is rewritten from the resolved source dir — while the file name was composed from the import path, so the emitted reference named a real directory and a file in it that exists nowhere: `…/vendor/golang.org/x/crypto/chacha20/golang.org.x.crypto.chacha20.csproj`. Deriving the name from the directory (`stdLibImportPathFromTargetDir`, applied in both stdlib arms of `importOperations.go`) is what makes the two halves agree *structurally* rather than coincidentally: `getProjectName` — the producer, which names the `.csproj` the vendored package actually emits — has always derived it from that same `GOROOT/src`-relative directory.

This is the third derivation in one family, and they must all resolve the vendored spelling or they disagree about which package is being named: the **namespace** (`resolveGorootVendoredPath`, under [Cross-package imports](#cross-package-imports-importing-another-package--assembly)), the **dependency-graph key** (`stdLibConverter`), and now the **project file name**.

Two consequences beyond the file name, because `PackageName` is not only a file name:

* It keys the embedded standard-library metadata, which records the vendored spelling (`##vendor.golang.org.x.crypto.chacha20`), so the unvendored name matched no section at all (asserted directly by the guard). Wherever that record is the source — a dependency with no `package_info.cs` on disk, i.e. a `-recurse=nuget` reference — the package's exported aliases and `GoImplement` records would have come back empty and silently fallen through to the derive-from-declarations path.
* It composes the imported-alias class path. The unvendored form yields `go.golang.org.x.crypto.chacha20_package`, which names a class that exists nowhere — the CS0234 family the namespace arm above exists to prevent. It was latent rather than active only because `loadImportedTypeAliases` dedupes on the *dependency's* `package_info.cs` path, which is the same file for both spellings, so whichever spelling was resolved first won and the other never applied its aliases.

**Where it surfaced, and what it did NOT do.** Only a `-tests` conversion could emit it: production emission resolves the vendored path upstream (`visitImportSpec`), while the test project's dependency list is the raw import set, which carries both spellings — so `crypto/ecdh`'s test project named the package twice, once correctly and once not. It is worth being precise about the damage, because a missing `<ProjectReference>` sounds fatal and is not: MSBuild degrades it to **warning MSB9008** and builds on (measured — the pre-fix `crypto.ecdh.tests.csproj` builds, 0 errors), and here the correct sibling reference supplied the assembly anyway. The real cost was downstream: the stale name was harvested into `go2cs-stdlib.slnx` as a phantom 308th project by the multi-platform merge's solution-recovery path (fixed on the solution side by `TestCollectConvertedProjectsIgnoresTestProjectReferences`; this is the emission half).

Guarded by `TestGorootVendoredReferenceNamesTheVendoredProject` (the vendored spelling, the metadata key, and the leaf package name), `TestStdLibImportPathFromTargetDir` (the recovery, including the non-core-rooted no-match that leaves the caller on the import path) and `TestStdLibReferenceUnchangedForUnvendoredPackage` (the no-op half — the whole corpus bar the `vendor/` tree, which is what makes a zero-movement [CNR](Glossary.md#cnr) verdict meaningful rather than lucky), all in `importOperations_test.go`.

### Generated output path: `$(OutDir)` defers to `$(BaseOutputPath)`

Both project templates (`src/go2cs/csproj-template.xml` and the `-tests` host's `test-csproj-template.xml`) give the generated project a stable default output path:

```xml
<!-- Build outputs are copied to $(OutDir), so this default must also defer to an explicit
     $(BaseOutputPath); otherwise that redirection silently never takes effect. -->
<PropertyGroup Condition="'$(OutDir)'=='' AND '$(BaseOutputPath)'==''">
  <OutDir>bin/$(Configuration)/$(TargetFramework)/</OutDir>
</PropertyGroup>
```

The **`AND '$(BaseOutputPath)'==''` half is load-bearing** and was added 2026-07-26. MSBuild copies build outputs to `$(OutDir)`, and `$(OutDir)` is what the SDK *derives* from `$(BaseOutputPath)` late in the import order — so a template that pins `OutDir` in the project body outranks any `BaseOutputPath` an outer `Directory.Build.props` sets, and that redirection is discarded without a warning. The generated project would keep writing to `bin\<Config>\<tfm>\` while `OutputPath` reported the redirected location, which is exactly how the defect hides.

Three separate isolation intents were silently defeated by the unconditional pin, all found at once:

- **`-tests` host output** — `test-csproj-template.xml` sets `BaseOutputPath=bin/tests/` (alongside `obj/tests/`) precisely so the test project and the production project that *shares its directory* do not collide. The `obj/` half worked; the `bin/` half never did, so every converted test host was writing into its production package's output tree.
- **The Native AOT perf publishes** — `src/tests/Performance/Directory.Build.props` sets `BaseOutputPath=bin\aot-build\` under its `PerfAot` gate. With the pin, the AOT publish's *build* step wrote through `OutDir` into the JIT tree and overwrote the JIT binary with a self-contained, `IsDynamicCodeSupported=false` one — which the runner then measured and published as the "JIT" column. See `docs/phase4/DESIGN-iface-shell-caching.md` §10.
- **End-user layouts** — any converted project consumed under an artifacts/CI convention that sets `BaseOutputPath` was ignored the same way.

Two guards pin this. `TestCsprojTemplateEmitsWellFormedXml` / `TestTestCsprojTemplateEmitsWellFormedXml` (`src/go2cs/csprojTemplate_test.go`) substitute each template exactly as its emitter does and stream the result through `encoding/xml`, which enforces the same comment rules MSBuild's loader does — a malformed template breaks the entire corpus at compile time and is otherwise only visible ~450 s into a full behavioral run. On the measurement side, `PerformanceRunner` reads the JIT binary's `runtimeconfig.json` before timing anything and **fails the run** if it is self-contained or has dynamic code disabled.

### Per-GOOS sources: layout L3 and `$(GoTargetOS)`

Go selects its platform sources at *build* time — filename suffixes and `//go:build` constraints — so a
conversion does not merely target a platform, it **is** that platform. A converted package whose emission
varies across `GOOS` therefore keeps the varying files in per-GOOS subfolders, and the `.csproj` compiles
exactly one of them; files that are byte-identical on every platform stay flat. This is layout **L3**
([`phase4/DESIGN-multiplatform-corpus.md`](phase4/DESIGN-multiplatform-corpus.md) §8, accepted 2026-08-08).
`internal/goos` is the first package to carry it:

```
src/core/internal/goos/goos.cs                    shared by windows, linux and darwin
src/core/internal/goos/package_info.cs            shared
src/core/internal/goos/windows/nonunix.cs         public const bool IsUnix = false;
src/core/internal/goos/windows/zgoos_windows.cs   public static readonly @string GOOS = @"windows"u8;
src/core/internal/goos/linux/unix.cs              public const bool IsUnix = true;
src/core/internal/goos/linux/zgoos_linux.cs       public static readonly @string GOOS = @"linux"u8;
src/core/internal/goos/darwin/…
```

Such a package's `.csproj` gains exactly two blocks — the selector, and the include that reads it:

```xml
<PropertyGroup Condition="'$(GoTargetOS)'==''">
  <GoTargetOS>windows</GoTargetOS>
</PropertyGroup>
…
  <Compile Remove="**/*.cs" />
  <Compile Include="*.cs" />
  <Compile Include="$(GoTargetOS)/*.cs" />
```

The include must follow `<Compile Remove="**/*.cs" />`, which would otherwise remove it — MSBuild evaluates
items in document order. The property may sit anywhere (properties are evaluated in an earlier pass) but is
declared above the item that reads it. `windows` is the default because the corpus that exists today is the
Windows emission, so a plain `dotnet build` reproduces the single-platform package this layout replaced:
verified byte-identical, and `-p:GoTargetOS=windows` produces the same assembly as the property absent.

**Only a package that varies gets the blocks.** The design measures the varying set at 37 of 304; the rest
emit the same C# on every platform and keep exactly the project file they always had.

**Which files are per-GOOS cannot be decided by one conversion.** The platform axis is a comparison of
several targets' *emissions*, and it is emphatically not derivable from Go file sets: four packages emit
different C# from identical Go source (constant folding, escape analysis, cross-file collision renaming,
dead-branch folding), and three emit identical C# from differing Go source. So the layout is produced by a
multi-target run — `go2cs -stdlib -comments -platforms windows/amd64,linux/amd64,darwin/amd64` — which
converts once per target into a seeded staging root, classifies every emitted artifact (shared / variant /
partial / exclusive), and merges the result into one tree.

A **single**-target conversion instead *honors* a layout the output tree already carries: if the package
directory holds `<goos>/<name>.cs`, that is where `<name>.cs` is written, and a package directory holding
any per-GOOS source folder gets the two blocks. That is what makes an ordinary seeded reconvert reproduce
an L3 package file for file, instead of laying a flat duplicate beside the copy the project is already
compiling — a duplicate-member break that would otherwise arrive silently. A per-GOOS folder is
distinguished from a nested package by the project file every converted package directory holds and a
source folder never does: `internal/syscall/windows` is a real package whose own directory name is a GOOS.

Guarded by `platformLayout_test.go` (`src/go2cs`), that negative case included.

**A HAND-OWNED file has a platform too, and the classifier cannot see it.** L3 decides placement by
comparing *emissions* — and a hand-owned file is never emitted, so it is never classified, so it silently
keeps whatever placement it had while the file it belongs to moves per-GOOS. That is invisible on Windows,
where the compile item set is identical either way, and it took the entire Linux corpus down at increment 3:
`runtime/lock_sema_impl.cs` supplements `lock_sema.cs`, which Go selects on Windows *and* macOS but never on
Linux, so the Linux build compiled the flat companion against a principal that was not in its build.

The rule is stated as a platform **set**, not as a folder: *a hand-owned file belongs in exactly the platform
builds its principal takes part in*, after which L3's ordinary placement rule applies unchanged — every
platform ⟹ flat, a subset ⟹ one copy per platform in the subset. Read literally as "inherit the principal's
folder", `os/proc_impl.cs` and `syscall/syscall_impl.cs` would be triplicated: their principals are per-GOOS
*variants* present on all three platforms, so three copies of one hand-written file would have to be
maintained in lockstep for no compile benefit. L3 duplicates only what cannot be shared.

A principal comes in two shapes, and both are already recorded in the tree by the emission itself:

| Hand-own | Principal | Why |
|:--|:--|:--|
| `<name>_impl.cs` | `<pkg>/<name>.cs` | a companion with no Go counterpart of its own; it *supplements* the converted file |
| `<name>.cs` carrying `[module: GoManualConversion]` | `<pkg>/<name>.cs.auto` | the conversion still runs and only its EMISSION diverts, to the review sibling — which is therefore emitted by exactly the platforms that compile the Go file this hand-own replaces |

The second binding is the one that catches a whole-file hand-own of a platform-exclusive Go file:
`syscall/dll_windows.cs` and `syscall/exec_windows.cs` replace Windows-**only** sources and now live in
`syscall/windows/`. The `.cs.auto` sibling moves with its `.cs` so the pair cannot separate — which is also
what a single-target reconvert already does, since `conversionDriver` routes both through
`platformLayoutPath`. A hand-own whose principal no target emitted (`runtime/managed_impl.cs`,
`internal/poll/runtime_sema_impl.cs` — go2cs machinery with no Go file behind it) has no placement evidence
and is left alone. Two existing copies of one hand-own that disagree are an **error**, never a first-wins
choice: a duplicated hand-own is hand-maintained in each folder, so propagating one over the other is how a
fix applied to a single flavor would disappear.

⚠ **A hand-owned FUNCTION is a different problem with no layout answer.** `manualConversionFuncs` is keyed by
NAME and is platform-blind, so an entry turns its Go declaration into a placeholder on *every* platform while
the implementation exists only where one was written — and `notetsleep_internal` is even four arguments in
`lock_sema.go` against two in `lock_futex.go`. Census and remedy in
[`phase4/DESIGN-multiplatform-corpus.md`](phase4/DESIGN-multiplatform-corpus.md) §7.

Guarded by `platformHandOwn_test.go`, which walks the **real** `src/core` rather than a synthetic tree —
the next offender will be a file somebody adds by hand. Three structural rules: an `*_impl.cs` whose
principal is in some but not all of its package's per-GOOS folders must be in exactly those; a `.cs.auto`
lives beside the `.cs` it reviews; and a source carrying Go's own GOOS filename constraint (`*_windows.cs`,
`*_linux.cs`, `*_darwin.cs`, with an `_impl` suffix stripped first) is never flat in an L3 package. That
third rule exists because a static walk cannot find a *marked* hand-own's principal — it is what would have
caught `dll_windows.cs`/`exec_windows.cs` a whole increment earlier.

**References are conditioned by the same rule, one level up.** A package's *direct imports* can differ by
`GOOS` too — measured at **21** packages, `os` being the clearest: it imports `internal/syscall/windows` on
Windows and `internal/syscall/unix` on Linux and macOS. One flat reference list cannot say that, so the
references common to every platform stay unconditioned and only the differences are selected:

```xml
  <ItemGroup>
    <ProjectReference Include="$(go2csPath)core/golib/golib.csproj" />
    …the 16 references os has on every platform…
  </ItemGroup>

  <ItemGroup Condition="'$(GoTargetOS)'=='darwin'">
    <ProjectReference Include="$(go2csPath)core/internal/syscall/unix/internal.syscall.unix.csproj" />
  </ItemGroup>

  <ItemGroup Condition="'$(GoTargetOS)'=='windows'">
    <ProjectReference Include="$(go2csPath)core/internal/godebug/internal.godebug.csproj" />
    <ProjectReference Include="$(go2csPath)core/internal/syscall/windows/internal.syscall.windows.csproj" />
  </ItemGroup>
```

**A platform whose delta is empty still gets a group**, written self-closing
(`<ItemGroup Condition="'$(GoTargetOS)'=='linux'" />`). That is not noise, and it is the one detail the whole
mechanism rests on: the shared list is an *intersection*, and a single-target reconvert recovers the other
platforms' sets from the file it is about to overwrite. Forget that linux takes part and the next reconvert
computes the intersection over two platforms instead of three — promoting a Windows-only reference into the
shared list, where it would land in the Linux build. The empty group records membership so the axis survives.

Both producers — the multi-target merge and a single-target reconvert — go through one renderer, which is
what makes the reconvert reproduce the merge's bytes rather than something merely equivalent. When every
platform's imports agree again the block disappears and the project file returns to its plain form.

Two facts that are NOT references still have to be reconciled, because one `.csproj` serves every platform:
`<AllowUnsafeBlocks>` is emitted as the **union** across targets (it differs in `os/user` and `syscall`; the
property grants a capability rather than using one, so raising it is inert where unused), and every other
companion artifact — README, icons, `.cs.auto` — is taken from the **first target in `-platforms` order**,
the reference flavor, so the choice is deterministic instead of depending on which target happened to have
something to rewrite.

Guarded by `platformProject_test.go`, whose central test is the invariant itself: a single-target reconvert
of every platform must reproduce the merged project file byte for byte.

### Build-warning suppression: what the emitted `.csproj` silences, and what it deliberately does not

Both templates carry one suppression policy, and it is a policy rather than an accretion: every entry is a
diagnostic that is **structural to the Go emission model**, and every diagnostic that is *not* is left
visible on purpose. The full census, code by code, is [`docs/phase4/DESIGN-warning-suppression.md`](phase4/DESIGN-warning-suppression.md).

```xml
<Nullable>annotations</Nullable>
<NoWarn>CS0162;CS0164;CS0282;CS0660;CS0661;CS1717;CS1718;CS8618;CS8860;CS8974;CS8981;IDE0060;IDE1006;CA2255</NoWarn>
```

The `NoWarn` entries each name an emission the converter re-creates on the very next conversion: Go type
names are lower-cased ASCII (`CS8981`, and `CS8860` for a Go type literally named `record`); a struct's
fields are split between its type file and `package_info.cs` (`CS0282`); Go comparison operators are emitted
on value types without `Equals`/`GetHashCode` (`CS0660`/`CS0661`); Go zero values leave non-nullable fields
uninitialized (`CS8618`); `if (raceenabled)` bodies and the `break;` appended after a `case` that already
`throw panic(…)`s are unreachable (`CS0162`), as are the synthetic `continue_<label>:`/`break_<label>:`
pairs emitted for every labeled Go statement (`CS0164`); the named-return store on the `goto ᒐdone` path
through a defer frame is a self-assignment (`CS1717`); `return f != f;` is Go's NaN idiom verbatim
(`CS1718`); a function value in a `map[string]any` looks like a forgotten call (`CS8974`); and Go `init()`
is modeled as a `[ModuleInitializer]`, which the library-hygiene analyzer objects to by design (`CA2255`).
`IDE0060`/`IDE1006` never fire at the command line — they exist for Visual Studio's live analysis, where
every `_`-shaped parameter and every Go identifier would otherwise be flagged.

**`Nullable` is `annotations`, not `enable`.** Go has no non-nullable pointer, interface, map, slice,
channel or func — every one of them is nil-able by construction — so C#'s nullable *flow analysis* is
asking a question the source language cannot pose, and the only way to satisfy it would be to annotate the
whole emitted corpus `?`, burying the Go shape the project exists to preserve. Nor is it protecting a
semantic go2cs wants: a converted program that dereferences a nil Go value *should* fault, because that is
Go's nil-pointer panic. `annotations` keeps `?` meaningful (golib's `ж<T>?`, `PanicException?`) and keeps
`default!` legal while turning the analysis off; `disable` would be wrong, because it makes every `?` in
the emitted code a fresh `CS8632`. One consequence is load-bearing: `CS8618` stays in `NoWarn` even under
`annotations`, because `go2cs-gen` emits `#nullable enable` at the top of each generated `.g.cs` and a
file-level directive beats the project property.

**The publish properties are scoped off `Library`.** `PublishReadyToRun`/`PublishTrimmed`/
`IncludeNativeLibrariesForSelfExtract`/`EnableCompressionInSingleFile` sit under
`Condition="'$(OutputType)'!='Library'"`, because the SDK turns `PublishTrimmed` into
`EnableTrimAnalyzer=true` **at build time** — so on a library, which is never published, that one line was
the sole source of every `IL####` warning in the corpus while buying nothing. A converted `main` package
still gets the analysis, and the trimmer re-runs over the whole closure at app publish, where it is
actionable. `AllowUnsafeBlocks` shares that group's history but **not** its condition: it is a compile
setting, and the converted stdlib is full of library packages that do not compile without it.

Codes deliberately left **visible** are the other half of the policy — `CS0219` (dead named-return locals,
the one static signal for a genuinely dropped assignment to a named result), `CS8778` (a live 32-bit
truncation in `nint`-typed `int64` constants), `CS0675`, `CS8500` (the managed-referent hazard the S1 fork
ruling is about — golib suppresses it locally, the corpus must not inherit it), `CS8826`, `CS0252`,
`CS0649`, `CS1522`. Each is a converter or golib defect wearing a warning's clothes; suppressing them would
delete the signal rather than fix the emission.

Six `.csproj` are hand-owned and carry the policy by hand rather than by emission — `core/unsafe` and
`core/testing` (skip-listed packages), `core/internal/godebug`, `core/internal/weak` and
`core/internal/concurrent` (whose only Go file is fully hand-owned, so `unmarkedFileCount == 0` makes the
driver `continue` before `writeProjectFile`), and `core/golib`. golib keeps a *shorter*, deliberately
different list: it is hand-written, it is the reflection/unsafe core, and its trim and nullable warnings
are a real to-do list with an owner rather than emission noise.

Three guards pin all of this in `src/go2cs/csprojTemplate_test.go`:
`TestBothCsprojTemplatesCarryTheSameSuppressionPolicy` asserts the two templates agree on `Nullable` and on
the `NoWarn` set *exactly* (so adding a code forces the design-doc update, and dropping one fails), and
`TestPublishPropertiesAreScopedOffLibrariesButAllowUnsafeBlocksIsNot` parses the rendered project and pins
both halves of the publish-group split — a regression that moved `AllowUnsafeBlocks` under the condition
would break the corpus in a way no warning count would reveal.

### Cross-package imports (importing another package / assembly)

When a package imports another and uses its exported surface, the converter must agree, on both the **producer** side (converting the imported package) and the **consumer** side (resolving the `import`), on the imported package's C# `(namespace, class)` and emit a `ProjectReference` to its generated `.csproj`. The package class is `<packageName>_package` and the namespace is the root `go` plus the import path's leading segments, so the two sides line up when the Go package name equals the import path's last segment (the usual layout: `import "x/y/barlib"` → package `barlib` → `go.x.y.barlib_package`). The consumer emits `using barlib = …barlib_package;` and references members as `barlib.Thing`.

Resolving *where* an imported package lives is **module-aware**: the standard library is found under `GOROOT` and mapped to `$(go2csPath)core/<pkg>`, but a **local/user module** (reached via a `go.mod` `replace`, or simply co-located in the same module tree) is invisible to the legacy `go/build` GOPATH resolver. The converter therefore falls back to the dependency directory captured from the module-aware `go/packages` load, treats that package's converted output as **in-place** (co-located with its Go source), and emits a `ProjectReference` **relative to the referencing project** (e.g. `../barlib/barlib.csproj`) so the generated `.csproj` is portable. This is what makes "import a sibling package and use it" compile as separate assemblies.

**Exported type aliases cross packages.** A package-level Go type alias that is exported — `type Temperature = Celsius` — is recorded in that package's `package_info.cs` as an assembly attribute in its `<ExportedTypeAliases>` block:

```csharp
// <ExportedTypeAliases>
[assembly: GoTypeAlias("Temperature", "go.CrossPkgLib_package.Celsius")]
// </ExportedTypeAliases>
```

A consumer that imports the package and names `CrossPkgLib.Temperature` cannot use a C# member-access for it (C# has no namespace-level type alias). Instead, the converter parses the imported package's `package_info.cs`, reads its `[GoTypeAlias]` attributes, and emits a corresponding `global using` into the consumer's own `<ImportedTypeAliases>` block — keyed by a package-qualified name whose `.` separator is the extended-Unicode dot `ꓸ` (`ꓸ`, a valid C# identifier character), since `CrossPkgLib.Temperature` is not a legal C# identifier:

```csharp
// <ImportedTypeAliases>
global using CrossPkgLibꓸTemperature = go.CrossPkgLib_package.Celsius;
// </ImportedTypeAliases>
```

The consumer's converted code then refers to the alias as `CrossPkgLibꓸTemperature`. (This round-trip depends on the module-aware resolution above to locate the imported package's `package_info.cs`; a stdlib dependency is found under the `core` output tree, a local module via its `go/packages` directory.) Guarded by the `CrossPkgLib`/`CrossPkgUser` cross-package behavioral test pair.

**A collision-renamed alias chain resolves to its concrete target.** When an exported type's name *collides* with a method name it is `Δ`-renamed (see [Type-vs-Method Name Collisions](#type-vs-method-name-collisions)); and when that type is *also* an empty interface — `type Token any` colliding with a `Token()` method, encoding/json's shape — the producer's `package_info.cs` carries a **two-hop chain**. The collision analysis records `Token → ΔToken`, and `visitTypeSpec` (which renders an empty-interface target as `object`) records the renamed declaration `ΔToken → object`:

```csharp
[assembly: GoTypeAlias("Token", "ΔToken")]
[assembly: GoTypeAlias("ΔToken", "object")]
```

A consumer that resolves only the FIRST hop and then qualifies the intermediate `Δ`-name as a package member emits `global using jsonꓸToken = go.encoding.json_package.ΔToken;` — but `ΔToken` is an assembly-scoped `global using`, **not** a namespace member of `json_package`, so it is CS0426 (encoding/json's `Token` consumed by html/template, internal/coverage/cfile, expvar, log/slog, internal/fuzz, …). The imported-alias loader (`loadImportedTypeAliases`) therefore follows the chain within the producer's OWN exported aliases to its **concrete** target, emitting `global using jsonꓸToken = object;`. A chain whose final target is a real `Δ`-renamed member (a delegate/struct such as `ΔFilter`, which is *not* itself an exported alias) stops there and stays package-qualified, unchanged. Guarded by the `CrossPkgLib`/`CrossPkgUser` pair — an empty-interface `Token` colliding with a `Sensor.Token()` method, named as a `var` type in the consumer and its boxed value read back, output-compared vs Go.

**Imported stdlib alias metadata loads from the tree the assembly is COMPILED from — which since 2026-08-01 is the only tree there is.** *(Resolved; kept because the failure mode is instructive.)* The alias round-trip locates the imported package's `package_info.cs` under the `core` output tree. While the converted standard library lived in a SECOND tree, a `-tests` build compiled its stdlib dependencies from `go-src-converted` while `loadImportedTypeAliases` still read `package_info.cs` from the baseline `core` stub — and most stubs had **no** `package_info.cs` at all (`runtime` was impl-stubs only), so the alias map came back empty and a test's cross-package reference to a collision-renamed stdlib type rendered the RAW, undefined qualified name: `err.(runtime.Error)` → `runtime.Error` (CS0426) instead of `runtimeꓸError` → `runtime_package.ΔError` (math/bits' `Div` overflow/divide-zero panic asserts). The fix at the time derived the alias-load directory from the project-reference remap itself, so the two authorities could not drift. Both the remap and that derivation are now **deleted**: the converted stdlib lives at `$(go2csPath)core/<pkg>`, exactly where every resolver already pointed, so the alias-load path and the compile path are the same path by construction. The lesson outlives the machinery — *metadata must be read from the tree the code is compiled from, never from a parallel one that merely looks like it.*

**A same-named cross-package alias target is fully qualified.** Two *different* packages can share a Go package name — html/template and text/template are both `package template`. When such a package aliases the other's type — html/template's `type FuncMap = template.FuncMap`, whose target lives in text/template — the alias RHS must name the target's OWN `(namespace, class)`: `go.text.template_package.FuncMap`. `getFullyQualifiedTypeName` had gated its cross-package branch on the package *name* (`pkg.Name() != packageName`), so a same-named foreign type read as *same-package* and fell through to the `t.String()` path, whose cross-package slash-strip drops BOTH the `text` path segment AND the `_package` class — emitting `global using FuncMap = go.template.FuncMap;` (CS0234; `template` is not a namespace of `go`). The check now compares package **identity** (`pkg != v.pkg`), matching `getAliasQualifiedTypeName` and `collectCrossPackagePaths`, so the branch fires and the target fully qualifies. (A code-*body* reference already rendered correctly — `getAliasQualifiedTypeName` keyed on identity — so only the `global using` alias RHS was wrong.) Guarded by the `CrossPkgSameNameAlias` behavioral test: a `package atomic` that aliases the same-named `sync/atomic`'s `Int32` (`type Int32 = atomic.Int32`), whose `global using` RHS must render `go.sync.atomic_package.Int32`, not the dropped-segment `go.atomic.Int32`.

**A `//go:linkname` VARIABLE pull becomes a forwarding property to the (publicized) remote.** Go's `//go:linkname local pkgpath.remote` on a bodyless package var aliases `local` to another package's `remote` — SAME storage, resolved by the linker. math/bits' `//go:linkname overflowError runtime.overflowError` (its `Div` panics with `overflowError`, a `runtime.Error`) emitted a null field, so the converted `Div` panicked with `null`. Go 1.23 requires the *definition* side to authorize the pull with a one-argument handle (`runtime/linkname.go`'s bare `//go:linkname overflowError`); the authorization is puller-AGNOSTIC, so the faithful C# emission of a handle-marked var is **`public`** (a puller in a separate assembly must reach it) — a purely local decision each package makes from its own directives, no cross-package coordination. The pulling var then emits as a **forwarding property** to the fully-qualified remote (resolves in `namespace go;` without a using), and the remote's package is queued for a project reference:

```csharp
// runtime (definition side, one-arg handle):
public static error overflowError = ((error)((errorString)(@string)"integer overflow"u8));
// math/bits (two-arg pull):
internal static error overflowError { get => go.runtime_package.overflowError; set => go.runtime_package.overflowError = value; }
```

Three safety gates keep the emission compilable, each narrowing forwarding to what C# can express (the rest keep the pre-feature null-field/heap-box form): (1) a handle var is publicized only when its **type is itself publicly accessible** — runtime's `sched` (`schedt`), `writeBarrier` (anon struct), `lastmoduledatap` (`*moduledata`) have unexported types and stay `internal`, since a public member cannot expose a less-accessible type (CS0052/CS0053) and such a var could not be pulled cross-assembly anyway; (2) a pull whose forwarding reference would form a **project-reference cycle** — `runtime` pulling `internal/syscall/windows.CanUseLongPaths`, where the target transitively depends on runtime — keeps its null field (Go's link-time linkname has no package cycle; a C# project reference cannot be circular; detected via the `-stdlib` dependency graph's `DependsOn`); (3) an **address-taken** pull — reflect's `//go:linkname zeroVal runtime.zeroVal` with `&zeroVal[0]` — keeps its addressed-global heap box, because a property has no address (`ᏑzeroVal` would be CS0103). The definition-side handle collection (`collectLinknameHandles`, a package-wide pre-pass like `collectPublicizedTypes`) and the pull recognition live in `linknameOperations.go`. (Guarded by the `LinknameVarPull`/`LinknameVarPullLib` behavioral test pair — a consumer package pulling an unexported handle var from a separate provider assembly, its value printed and output-compared vs `go run`; the full corpus compiles with the runtime handle vars publicized and the acyclic pulls forwarded. This is what unblocks math/bits' `Div` panic tests from `null`.)

**Exported structs and interfaces cross packages.** An exported struct's fields and methods are reachable on the consumer side exactly as the producer emits them — `CrossPkgLib.Sensor{Name: …, Temp: …}` lowers to a C# constructor call and `s.Name` / `s.Hot()` to field/method access on the imported struct — because the struct and its `[GoRecv]` extension methods live in the (referenced) library assembly.

A cross-package **interface satisfaction** is subtler. Go is structurally typed, so a consumer may assign any value with the right method set to an interface; C# requires the *nominal* `partial struct T : I` implementation glue, which the [`ImplementGenerator`](#source-generators) can only add to `T` **in T's own assembly** (`isLocalImplType`). The converter records a `[assembly: GoImplement<T, I>]` for each concrete→interface conversion it *witnesses while converting T's package* — so for a consumer to use `Sensor` as `Labeled` across the assembly boundary, the satisfaction must be witnessed in the **library** that declares `Sensor`. The idiomatic Go interface-satisfaction assertion does exactly this:

```go
var _ Labeled = Sensor{}   // in CrossPkgLib — records GoImplement<Sensor, Labeled> in this assembly
```

With that, the library emits `[assembly: GoImplement<Sensor, Labeled>]`, `Sensor : Labeled` is realized in the library assembly, and a consumer's `var l CrossPkgLib.Labeled = s` / `CrossPkgLib.Describe(s)` compile as ordinary upcasts. (A library that returns the interface from a constructor — `func New(...) Labeled { return Sensor{…} }` — witnesses it the same way.) A type that satisfies an interface but is *never* used as it within its own package gets no nominal glue — proactively recording every local concrete→local interface structural match WAS tried (a declaration-site scan, 2026-07) and was RETIRED on 2026-07-25, because it could never be complete (it cannot see a dynamic type in a later-converted assembly) and paid for its incompleteness in speculative glue. Such a satisfaction is resolved at run time by the interface's duck-typing shells instead; a *declared* conversion still takes the nominal fast path. Also guarded by the `CrossPkgLib`/`CrossPkgUser` pair (Phase 3: struct field access + interface satisfaction).

#### A sub-package import whose leading segment is a package alias root-qualifies
When a package imports both a parent package and its sub-package — testing/fstest importing `io` **and** `io/fs` — the converter emits `using io = io_package;` (a **type** alias for the io package class) and, for io/fs, a **relative** namespace target `io.fs_package`. In C# the leading `io` segment of `io.fs_package` binds to that type alias, so `io.fs_package[.FS]` resolves to the nonexistent nested type `io_package.fs_package[.FS]` — CS0426 (the `using fs = …` alias line, the embedded `fs.FS` getter, and the generated `TypeGenerator` copies). The converter records every direct-import using-alias identifier bound in the package and prefixes `go.` onto any multi-segment relative namespace/type whose leading segment is one of them, so the segment resolves as the child **namespace** it names:

```go
import (
    "io"
    "io/fs"
)
type fsOnly struct{ fs.FS }
```
```csharp
using fs = go.io.fs_package;
public go.io.fs_package.FS FS;
```

The unqualified `io.fs_package.FS` is retained as the `promotedInterfaceImplementations` map **key** (which feeds alias-less generator files where the relative form resolves). This complements the existing alias-vs-child-namespace Δ-rename, which only catches `<currentNS>.<alias>` collisions. (Recurs for any parent+sub-package import pair; a behavioral guard is owed — the io/fs embedded-interface pattern needs a parent+sub-package pair absent from the core baseline stdlib.)

The same collision detection must see GOROOT-VENDORED namespaces. `visitImportSpec` resolves a GOROOT package's `golang.org/x/…` import to its on-disk `vendor/…` path (and namespace) when the importing file lives under GOROOT, but `computeImportAliasRenames` built `packageChildNamespaces` from the raw `imp.Path()` — so a vendored sub-namespace like `go.vendor.golang.org.x.text.unicode` was absent from the map. `rootQualifyIfAmbiguous` then could not see that a stdlib alias's leading segment collides with it: bidirule (at `vendor/golang.org/x/text/secure/bidirule`, importing both `unicode/utf8` and the vendored `golang.org/x/text/unicode/bidi`) emitted `using utf8 = unicode.utf8_package;`, whose `unicode` bound to the in-scope vendored `unicode` namespace rather than stdlib `go.unicode` (CS0234). `computeImportAliasRenames` now applies `resolveGorootVendoredPath` to each closure path when the package lives under GOROOT — matching the emission — so the vendored namespaces populate the map and the alias root-qualifies to `go.unicode.utf8_package`. Gated on GOROOT so a user module's own `golang.org/x` dependency is untouched; **guard owed** (the fix fires only for a GOROOT-vendored package, which the behavioral harness — never under GOROOT — cannot express; validated by the bidirule reconvert [A/B](Glossary.md#ab)).

**A DOTTED build tag (`goexperiment.X`, `amd64.vN`) is matched against the host toolchain's tool tags.** The converter re-checks each file's `//go:build` constraint after `go/packages` has already loaded it (to drop files for the wrong GOOS/GOARCH when converting cross-platform). Its evaluator only handled bare identifiers (`linux`, `amd64`), so a *dotted* tag parsed as a selector and fell through to `false`. That is wrong for an experiment enabled BY DEFAULT: `coverageredesign`, `regabiwrappers`, and `regabiargs` are in the host's `go/build` `ToolTags`, so `go/packages` loaded their `//go:build goexperiment.X` `_on.go` files — but the re-check then re-EXCLUDED them (the selector → `false`), dropping the package-level consts (`testing`'s `goexperiment.CoverageRedesign`, CS0117 ×4). The evaluator now resolves a dotted tag by membership in `build.Default.ToolTags` — so an enabled experiment's `_on.go` survives and a disabled one's `!goexperiment.X` `_off.go` survives, exactly the one `go/packages` chose. Blast radius is only `internal/goexperiment` (the sole stdlib package whose file selection flipped). **Guard owed** — the fix depends on the host toolchain's active tool tags, which the `go2cs/*` behavioral harness cannot express portably; validated by the reconvert A/B (only `internal/goexperiment` changes) and the [census](Glossary.md#census) (the consts appear, `testing`'s CS0117 clear). (That tag lookup now lives in the single `matchTag` callback described next; it was a `*ast.SelectorExpr` case while the converter still parsed constraints itself.)

### Build constraints are parsed and evaluated by `go/build/constraint`

The re-check above needs a constraint parser, and for a long time the converter hand-rolled one: it lowercased the expression text and handed it to `parser.ParseExpr`, then walked the resulting `ast.Expr` itself. That parser is for **Go expressions**, and a build constraint is not one. A **Go release tag** is the case that breaks it — `go1.21` reads as the identifier `go1` followed by an illegal `.21` selector, so *every* constraint mentioning a release failed with `failed to parse build constraint: 1:4: expected 'EOF', found .21`. `conversionDriver` warns on that error and falls through to **including** the file, so the failure hid behind an accidentally-correct outcome for as long as the only constraints that hit it were bare `go1.N` gates (five `go-logr` files, surfaced by a `-recurse` probe of `go.opentelemetry.io/otel`). It stops being correct the moment a constraint mixes a release tag with a platform: `//go:build go1.21 && windows` converted for linux lost its platform half along with the rest of the expression.

Constraints are now parsed and evaluated by **`go/build/constraint`**, the package the toolchain itself uses, which retires the whole custom parse/eval layer rather than special-casing the dot:

* **Recognition** — `constraint.IsGoBuild` / `constraint.IsPlusBuild` decide what is a constraint line. Both require the comment at **column zero**, and only the file **header** is scanned (everything above the package clause, line comments only). The regex that preceded them matched any `//go:`-prefixed line *anywhere in the file*, so a `//go:build` quoted in documentation below the package clause gated the file it was describing.
* **Precedence** — a `//go:build` line wins outright; the legacy `// +build` lines apply only in its absence, ANDed across lines, with `,` meaning AND and a space meaning OR inside one. The old regex could not see `// +build` at all (no `//go:` prefix), so a legacy-only file — the norm in third-party modules predating Go 1.17, exactly what `-recurse` meets — converted as **unconstrained**.
* **Evaluation** — `Expr.Eval` drives one `matchTag` callback, so the boolean structure (`&&`, `||`, `!`, parentheses) is the stdlib's problem and go2cs owns only "is this one tag satisfied". Tags resolve in three layers: the `allowedPlatforms` map (GOOS, GOARCH, the derived `unix`/`posix`, the compiler tags, and every `-tags` value), then the Go release tags, then `build.Default.ToolTags` for dotted tags. Matching is **case-sensitive**, as the toolchain matches; the old evaluator lowercased the whole expression first, which quietly made a mixed-case `-tags MyTag` unsatisfiable — `SetTag` stored it verbatim but the lookup folded it.

**Release tags are asked of the go command, not of the compiled-in list.** This is the one place where "use `build.Default`" — right for `ToolTags` — is wrong. Under `GOTOOLCHAIN=auto` the go command re-execs a **newer** toolchain when the main module asks for one, so `go/packages` can be selecting files under Go 1.25 while the `build.Default.ReleaseTags` linked into a go2cs built with Go 1.23 stops at `go1.23`. The re-check would then call `go1.24` false where the loader called it true and drop the file — *and* the `!go1.24` sibling was already dropped upstream by the loader, leaving the package with **neither half** of a fallback pair. Over-exclusion is this pass's recurring failure mode (the `purego` seeding and the `goexperiment` branch above each exist to undo one) and it is the dangerous direction, because the loader has already applied the full constraint for the target platform — anything this pass subtracts is real code. So release tags come from `go env GOVERSION` run in the same directory `packages.Load` uses, expanded to `go1.1`…`go1.N`. An unreachable or unparseable toolchain falls back to the compiled-in list — never to an empty one, which would make every `go1.N` false.

That subprocess costs ~300 ms on Windows, so it is contained twice. Resolution is **lazy** — `matchTag` tests the `go1.N` *shape* before resolving anything, so a constraint naming no release tag never pays it; and the answer is then cached per **module root**, because GOTOOLCHAIN keys on the module. Both halves earn their place: the behavioral corpus is **569 separate modules**, so the cache alone would still spend 569 lookups answering a question not one of those packages asks (no behavioral constraint mentions a release tag), while `-stdlib` reaches the lookup exactly once — `sort` is the only standard-library package with a `go1.N` gate — and `GOROOT/src` carries one `go.mod` above all 302 packages anyway. Note this does not help the **type checker** go2cs links in, which is still whatever release compiled it; a module whose `go` directive exceeds that still needs go2cs rebuilt on a newer toolchain.

Guarded by `src/go2cs/buildConstraints_test.go` (release tags bare/negated/compound, the legacy `+build` grammar, extraction precedence, loader-toolchain resolution), each assertion verified to fail against the pre-fix converter.

### A blank import forces the imported package's `init` to run

Go's `import _ "image/png"` imports a package **purely** for the side effects of its `init`, and the
language guarantees that initializer runs before the importing package's own. A converted Go `init`
becomes `[GoInit]`, which `csproj-template.xml` aliases to .NET's `[ModuleInitializer]` — the right
shape and a **weaker guarantee**: a module constructor runs at first access to something in its
module, so an assembly nothing in the program ever *names* is never loaded and never initializes. A
blank import is by definition the case that names nothing, and the observable form is a registry that
stays empty. `image/gif`'s `writer_test.go` blank-imports `image/png` so that png's `init` calls
`image.RegisterFormat` (`image/png/reader.cs`); with the import emitted as a comment alone that never
ran, `image.Decode` had no PNG entry, and `TestWriter` failed with
`../testdata/video-001.png image: unknown format` — the package's only failure, at 27 of 28. The same
shape gates every registration-by-blank-import consumer: `database/sql` drivers (`sql.Register`),
`net/http/pprof` (its `init` installs the `/debug/pprof` handlers), `image/png` and `image/jpeg` as
decoders for anything calling `image.Decode`, and `time/tzdata`.

The blank import still emits **no `using`** — `using _ = <ns>;` would hijack C#'s `_` discard for the
whole file (CS0118 + CS0029 on any deconstruction discard) — so it stays a comment, and the
initialization is forced by a generated module-initializer hook at the top of the importing file's
class body:

```go
import (
    _ "BlankImportSideEffects/jpeglike"
    _ "BlankImportSideEffects/pnglike"
    "BlankImportSideEffects/registry"
)
```
```csharp
// blank import: BlankImportSideEffects.jpeglike_package (side effects only; no using emitted — a `using _` alias hijacks C# discards)
// blank import: BlankImportSideEffects.pnglike_package (side effects only; no using emitted — a `using _` alias hijacks C# discards)
using registry = BlankImportSideEffects.registry_package;

partial class main_package {

// Go runs a blank-imported package's `init` before this package's own; .NET would never
// load an assembly nothing references, so the side effects the import exists for are forced.
[GoInit] internal static void initᴛᴛblankImportꓸBlankImportSideEffectsꓸjpeglike() {
    builtin.initPackage(typeof(BlankImportSideEffects.jpeglike_package));
}
```

Four decisions make that emission what it is.

**The mechanism is `RuntimeHelpers.RunModuleConstructor`**, wrapped as golib's `builtin.initPackage(Type)`
so the emitted line stays readable and the mechanism lives in exactly one place. It is the explicit,
spec-defined way to run a module constructor, and the runtime guarantees a module constructor runs **at
most once** — so several blank importers of one package, or a package forced after it has already
loaded, are no-ops rather than repeated `init` calls. Measured under **Native AOT** as well as the JIT:
the call is AOT-safe, and under AOT the gap does not even arise (a single native image has no lazy
assembly load, so every linked module's initializers run at startup regardless) — the forced call is
simply redundant there. `typeof` also roots the package class for the trimmer, so a trimmed publish
keeps the assembly the program otherwise never names.

**The hook leads the class body**, ahead of the file's own `init` functions, because Go orders an
imported package's initialization before the importer's. Roslyn emits an assembly's module-initializer
calls in compilation file order and then declaration order within a file, so leading the file's
declarations is what that ordering buys — within one file it is exact. Across files of one package the
order is Roslyn's, which is the same latitude the conversion already lives with for every *non*-blank
import (whose initializer the CLR runs lazily at first use, not in Go's order); no converted package
depends on the difference. Reproducing Go's ordering in full would mean forcing **every** import
eagerly, in dependency order, from a single per-assembly driver — a strictly larger change that trades
startup cost (loading the whole transitive assembly closure at module init) for fidelity nothing
currently needs. Deliberately deferred, not overlooked.

**Exactly one hook per (assembly, imported package).** Go initializes a package once per program
however many files import it, and a .NET module constructor likewise runs once per assembly, so the
hook belongs to the first file that names the import; later files' blank imports of the same package
emit nothing. The hook's name is derived from the import path (`image/png` →
`initᴛᴛblankImportꓸimageꓸpng`), which makes it unique by construction — two blank imports in one file
are two methods, and a shared generated name would be CS0111 — and stable across runs without a
counter or a file-name mangle. Path segments are reduced to C# identifier characters, so a module
path's dots and hyphens (`github.com/mattn/go-isatty`) cannot break the identifier. The doubled temp
marker keeps the name clear of the relocated-package-var method space (`initᴛ<varname>`, see
[Package-Level Variable Initialization Order](#package-level-variable-initialization-order)) and the
`blankImport` word clear of the `-tests` package-init hook (`initᴛᴛtests`).

**Go's pseudo-packages are skipped.** `unsafe` and `builtin` are compiler-provided and have no
initialization at all, and `C` is cgo. `import _ "unsafe"` is the `//go:linkname` ritual — 67 files of
the converted standard library — so forcing it would be a guaranteed no-op emitted 67 times. Only real
packages get a hook, which is why the converted corpus's blast radius is three files
(`crypto/x509` → sha1/sha256/sha512, `runtime/metrics` → runtime, `runtime/race` → its amd64v1 variant)
rather than seventy.

**Only the module constructor is forced — that is exactly the package's `init` functions.** A package's
own package-level variable initializers are C# static field initializers on the package class, which
the CLR still runs lazily at first access to that class, unchanged from every other import (and the
package's own `init` touching them is what triggers them). The residual case is a package whose
registration is a package-level `var _ = pkg.Register(…)` rather than an `init`; closing it means
additionally forcing the package class's type initializer, which is deliberately *not* done here
because it would eagerly run `runtime`'s 291 package-level initializers on behalf of
`runtime/metrics`'s linkname-only blank import for no measured benefit. No blank import in the
converted standard library registers that way.

The `-tests` emission carries all of this unchanged — the hook is written by the same import visitor,
so a blank import in a `_test.go` file (which is where `image/gif`'s is) forces from the test assembly.
Guarded by the `BlankImportSideEffects` behavioral test — a `registry` package that two blank-imported
sibling packages fill from their `init`s, read back by an importer that never names either registrant,
with the importer's own `init` recording the count to prove the ordering and an unregistered name as
the negative control (without the hooks the program prints `count at init: 0` and three `missing:`
lines; with them it matches `go run` exactly) — plus the `TestBlankImportInitName` and
`TestNoInitPseudoPackages` converter unit tests, which lock the generated name's uniqueness and the
pseudo-package skip.

### A NuGet-referenced standard library carries its exported metadata IN THE CONVERTER

Everything above — the imported-type-alias `global using` round-trip, and the foreign `GoImplement`
records that tell a consumer its dependency's own assembly already implements an interface — is learned
by **opening the referenced package's `package_info.cs` and scraping the `[assembly: …]` lines out of it**
(`loadImportedTypeAliases` / `loadPackageImplements`, `importOperations.go`). That file is converted
*source*, found at `$(go2csPath)core/<pkg>`, i.e. wherever `deploy-core` staged the standard library.

`-recurse=nuget` removes exactly that file. In NuGet mode the standard library is consumed as
pre-compiled `go.<pkg>` assemblies, so there is no converted-source tree on disk — and there never will
be, since skipping the source deployment is the whole point of the mode. The scrape then finds nothing
and the converter falls through to `foreignDerivedTypeAliases`, which recovers *some* aliases from the
dependency's own Go declarations and **no implement records at all**. Two things go wrong, both in the
CONSUMING package:

* its `<ImportedTypeAliases>` block loses the dependency's exported aliases
  (`global using syscallꓸHandle = go.syscall_package.ΔHandle`, `netꓸAddr`, `timeꓸLocation`, …); and
* not knowing that `syscall`'s assembly already implements `error` on `syscall.Errno`, it **re-declares
  the pair locally** — `[assembly: GoImplement<syscall_package.Errno, error>]` — and wraps the value at
  the cast site in a locally-generated adapter (`new syscall_Errnoᴠerror(e)`) instead of converting
  implicitly.

`golang.org/x/sys/windows` shows both at once: it Go-aliases `type Errno = syscall.Errno`, so the two
spellings `Errno` and `syscall_package.Errno` were each recorded, resolved to the same type, and made
`ImplementGenerator` emit the `syscall_Errnoᴠerror` adapter **twice** — CS0102 / CS0111 ×5 / CS8646.
That single duplicated adapter is what made the README's `fatih/color` walkthrough unbuildable under
`-recurse=nuget`.

**Note what is NOT the cause.** The `go2cs-gen` generators are not reference-kind sensitive: a real
MSBuild build hands a `<ProjectReference>` to the compiler as a `PortableExecutableReference` exactly
like a `<PackageReference>`, and the generators read foreign *type shape* from symbols either way (see
`FindTypeSymbol`). Both modes ran the same generator over the same kind of reference; the emitted C#
they were given differed, because the **converter's** cross-package knowledge differed.

**The conversion.** The exported metadata is a static, per-package property of the *published* assemblies,
so it travels with the converter. `internal/genstdlibmeta` captures the `<ExportedTypeAliases>` section and
every `GoImplement` record of all 302 `src/core/**/package_info.cs` into
`src/go2cs/stdlib-metadata.txt` (~128 KB), which `stdlibMetadata.go` embeds via `//go:embed`. When a
stdlib dependency's `package_info.cs` is absent, the converter reads its recorded lines through the very
same parsers (`parseExportedTypeAliasLines` / `parseExportedPointerImplementLines` /
`parseExportedValueImplementLines`, refactored off the file read for this) and proceeds identically. The
asset is generated from the same tree `push-nuget.ps1` packs, so the embedded record and the published
assemblies are always one commit's output.

Reading the record is gated on `PackageInfo.PublishedStdLib` — `isStdLib && nugetRefs && !convertStdLib` —
and this gate is a **soundness precondition, not conservatism**. The record describes the converted
standard library at `src/core`; substituting it is only correct when that is what the build actually
references. Under `-recurse=nuget` that is guaranteed. A `$(go2csPath)` deploy root may hold an older or
partial staging whose exported surface differs, and the stdlib
self-conversion is *building* the assemblies being published. Both keep the derive-from-declarations
fallback. Because `-recurse=nuget` is off by default, no other conversion path changes — CNR is
byte-identical across the behavioral corpus.

The result is exact rather than approximate: converting the README's `colordemo` with `-recurse=nuget`
against an empty `$(go2csPath)` now emits **byte-identical** C# to the same app converted with
`-recurse` against a full `deploy-core stdlib` root, and the NuGet-referencing solution builds with 0
errors and runs with output matching `go run`. `syscall.Errno` is not a special case — a probe returning
six foreign concrete types as foreign interfaces recovered 14 imported aliases and dropped three spurious
re-declarations spanning both record forms (`io/fs`'s `PathError`→`error` **pointer** adapter, `sort`'s
`IntSlice`→`Interface` **value** implement, and `syscall.Errno`), leaving only the one record the
consumer legitimately owns (`os.File` → `io.Reader`), exactly as the source-referencing conversion does.

Five guards (`stdlibMetadata_test.go`). `TestStdLibMetadataAssetFileName` pins the generator's output name
against the `//go:embed` target (and its `package_info.cs` constant against the converter's), so the two
halves can never write and read different files. `TestStdLibMetadataInSync` regenerates the asset in-process from
`src/core` and fails on drift — a stale asset would hand `-recurse=nuget` the *previous*
standard library's records while the published assemblies carry the current ones, surfacing only as
downstream C# errors. `TestStdLibExportedMetadataReadsThroughPackageInfoParsers` pins that the embedded
lines feed the shared parsers (and that a `Pointer`-form record does not leak into the value implements).
`TestPublishedStdLibScope` pins the three-way gate above. `TestRecurseNuGetResolvesForeignImplements`
converts a module returning a `syscall.Errno` as an `error` with no converted stdlib on disk and asserts
the consumer records nothing and emits no local adapter — it fails on both assertions with the record
disabled.

**Residual limitation.** The asset is regenerated by `go generate .` from `src/go2cs`, so a change to the
converted standard library must be re-banked into `src/core` before it reaches
`-recurse=nuget` consumers. `TestStdLibMetadataInSync` makes that a test failure rather than a silent
mismatch, but it can only compare against the *committed* tree — it cannot detect that the committed tree
is itself older than the published packages.

### A foreign implement record is keyed in ONE spelling, and a VALUE one is trusted only for a partial struct

The record scraped above answers one question at a cast site: *does the dependency's own assembly
already implement this pair?* If it does, the bare value converts implicitly and a local
`<pkg>_<T>ᴠ<Iface>` value adapter is dead machinery. Getting the answer wrong in either direction is
expensive, so both halves — the KEY and the TRUST — are stated precisely here.

**The key.** `implementRecordKey` composes `<declaring package>|<C# simple type>|<pkg>_package.<Iface>`
and is called by BOTH sides of BOTH record sets: `loadPackageImplementLines`, over records parsed from a
dependency's `package_info.cs`, and the value arms *and* the foreign-pointer arm of
`convertToInterfaceType`, over a cast being converted. That it is one function is the whole point — the
two sides used to compose it independently, over different alphabets, and agreed only when the
dependency's import path was a single segment:

| dependency | load side | use side | |
|:--|:--|:--|:--|
| `io` | `io\|noBody\|io_package.ReadCloser` | `io\|noBody\|io_package.ReadCloser` | match |
| `encoding/binary` | `binary\|bigEndian\|binary_package.ByteOrder` | `binary\|bigEndian\|encoding.binary_package.ByteOrder` | **miss** |
| `image/color` | `color\|ΔRGBA\|color_package.Color` | `color\|RGBA\|image.color_package.Color` | **miss** |
| `text/template/parse` (ptr) | `parse\|ListNode\|parse_package.Node` | `parse\|ListNode\|text.template.parse_package.Node` | **miss** |
| `go/types` (ptr) | `types\|TypeName\|go.types_package.Object` | `types\|TypeName\|types_package.Object` | **miss** |
| `image` (ptr) | `image\|ΔRGBA\|image_package.Image` | `image\|RGBA\|image_package.Image` | **miss** |

Two divergences, and the second is easy to miss because it only shows on a collision-renamed type.
(1) The INTERFACE side: a parsed record names the recording package's own interface BARE and a foreign
one whole (`go.image.color_package.Color`), while a cast site always renders the full namespace chain.
`canonicalImplementRecordIfaceName` drops everything ahead of the `<pkg>_package` segment, so both reduce
to `color_package.Color`; a member path under the class (`y_package.Outer.Inner`) survives intact. The
package CLASS must stay — the simple name alone collides, and image's `Paletted`→`image.Image` record
must not satisfy a `Paletted`→`draw.Image` cast. Note that neither side is reliably the *longer* one, so
a "strip the chain" heuristic would not do: `go/types` records its OWN `Object` fully qualified where the
cast site renders it short, while `text/template/parse` records its own `Node` bare where the cast site
renders it whole. Which spelling a file produces depends on its own using/alias context — which is
exactly why a canonical form, and not either side's raw text, is the key. (2) The TYPE side: a record
carries the EMITTED C# name, so image/color's `RGBA` (collision-renamed against its own `RGBA()` method)
is `ΔRGBA` there, while the use side was naming the GO type. Both sides now reduce the emitted name.

The DECLARING-package component is what keeps a record honest. A package may record a value pair for a
type declared in a THIRD assembly — `image` re-declared all of image/color's models — and go2cs-gen
realizes that as a local adapter class, not as the type implementing the interface. The use side names
the TARGET's package, so such a record can never satisfy a cast (`image|Alpha|…` against
`color|Alpha|…`).

**The trust.** A record says the declaring assembly implements the pair; it does not say HOW.
`ImplementGenerator` makes every named Go type a `partial struct T : Iface` that really does implement
it — struct, slice (`[GoType("[]Color")] partial struct Palette`), map, channel, numeric
(`[GoType("num:nint")] partial struct ΔSignal`) — with exactly one exception: a named FUNC type arrives
as a C# **delegate**, which cannot be a partial struct, so its `TypeKind.Delegate` arm emits an adapter
CLASS in the declaring assembly instead. `valueRecordRealizesAsPartialStruct` gates on the target's Go
underlying being a non-`*types.Signature`, at the use site where `go/types` can still see it. Without
that gate the fix hands a bare delegate to an interface slot — CS0029 for net/http's
`HandlerFunc` → `ΔHandler` in `expvar`, `net/http/cgi` and three more.

**The POINTER set shares the key and needs no trust gate.** `[assembly: GoImplement<T, Iface>(Pointer =
true)]` is not "the declaring assembly implements this somehow" — it is exactly the shape
`ImplementGenerator` realizes as the public adapter class `<T>ж<Iface>`, so the record's existence *is*
the answer and there is nothing further to ask. (The delegate hazard that forces
`valueRecordRealizesAsPartialStruct` on the value side cannot arise: a pointer record already means the
adapter route was taken.) An earlier ruling kept this set's key un-collapsed on the reasoning that
matching a foreign record suppresses a LOCAL record the consumer needs; that hazard is real but it is a
*realization* question, not a *key* question, and on the pointer side it does not exist at all.

One consequence had to be fixed with the key, and only the collision-renamed types reach it: a foreign
type whose name is Δ-renamed resolves through a whole-TYPE `global using` alias (`imageꓸRGBA =
go.image_package.ΔRGBA`), which is a single identifier rather than a path — and the adapter is a MEMBER
of the declaring package's class, so composing onto the alias names nothing (`imageꓸRGBAжImage`, CS0246
×11 across five packages). The foreign-adapter arm therefore rebuilds a dotless base as the file's
package qualifier plus the type's EMITTED simple name — `image.ΔRGBAжImage`, which is exactly what the
declaring assembly's generator composed (`image/image.cs` reads `new ΔRGBAжImage(…)` for its own casts).

**Pointer footprint,** from a whole-stdlib A/B with both roots seeded (304/304 converted per side):
31 files, **66 constructions**, every one the same edit — `new <pkg>_<T>ж<Iface>(x)` becomes
`new <pkg>.<T>ж<Iface>(x)`, the declaring package's own adapter — plus the 37 `(Pointer = true)` records
that existed only to generate those local classes. Zero additions anywhere and the total adapter-
construction census is unchanged at 4348, so it is a one-for-one redirection, not a removal. By
declaring package: `text/template/parse`→`Node` 33, `go/types`→`Object`/`ΔType` 20, `image`→`Image` 4,
`net/http`→`RoundTripper`/`ΔHandler` 4, `net/url`→`error` 2, `net/textproto`→`error` 1,
`go/internal/srcimporter`→`types.Importer` 1, `go/build/constraint`→`Expr` 1. `go2cs-stdlib.slnx`
builds 0 errors on the overlaid tree.

The pointer form's symptom is milder than the value form's and worth stating precisely, because it is
what makes this an increment rather than a bug fix. The generated adapter's `Equals` compares
`IжAdapter.Box` by reference, so a redundant local adapter and the declaring assembly's own one still
compare equal and still alias the same object — no observable divergence was reproduced. What is wrong
is duplication plus a **non-deterministic dynamic type**: each adapter's module initializer calls
`AdapterRegistry.Register(typeof(ж<T>), typeof(Iface), …)`, which is first-wins, so which assembly's
class a type-assert re-wraps into depends on assembly load order. The value form's second-identity
failure (`image/png`'s `%v`, below) is the same defect one degree worse.

**Footprint,** from a whole-stdlib A/B with both roots seeded (302/302 converted per side): 13 files,
every changed line the same edit — `new <pkg>_<T>ᴠ<Iface>(x)` becomes `x` — plus the 16
`[assembly: GoImplement]` records that existed only to generate those adapters. **497 constructions**
go away (472 of them in `image/color/palette`'s two palette literals); the rest of the corpus adapter
census is identical count for count, `HandlerFuncᴠΔHandler` included. Two survivors are instructive
because they are NOT this defect: `color.Palette`→`color.Model` (5) and `encoding/binary`'s
`bigEndian`/`littleEndian`→`ByteOrder` (79) have no record to match at all — neither package ever
converts that pair itself, so nothing writes the record and the local adapter is the only realization.
A pair a package satisfies but never records is a separate root, closed by the section below.

This is not merely a wasted allocation. The adapter is a **second identity** for one Go value: `reflect`
and `fmt` see the wrapper where the Value's own type says the wrapped struct, which is how it surfaced —
`image/png`'s `diff` printing `%v` of a `color.Color` died with `System.ArgumentException: Field 'R' … is
not a field on the target object which is of type 'go.image_package+color_NRGBAᴠColor'`. (Guarded by the
`ForeignValueImplementSuppression` behavioral test — a sibling package at a multi-segment path that
converts its own values, a collision-renamed implementer, a second implementer, and a named FUNC type as
the live negative; the pre-fix converter emits five adapters where the fixed one emits the func's alone.
`ValueAdapterDynamicType` was its byte-identical complement — its sibling never converts, so its four
adapters were real — until the declaring side began recording pairs it merely satisfies (next section),
which is exactly that sibling's shape; its assertions now prove the bare value instead. The pointer form
is guarded by `ForeignPointerImplementSuppression`, whose sibling `tone` self-converts a
collision-renamed `*Tone` and an ordinary `*Plain` — both must reference tone's own adapters — against
two live negatives that must keep minting their own: `*Lone`, a pair `tone` satisfies but never records,
and `shade.Level`, an interface with the same SIMPLE name as `tone.Level`. The pre-fix converter emits
four local adapters there where the fixed one emits the two negatives' alone. Unit-guarded by
`TestImplementRecordKeyBothCompositionsAgree`,
`TestImplementRecordKeyKeepsPackageClassDiscrimination` and `TestValueRecordRealizesAsPartialStruct`.)

### A package records the pairs it SATISFIES, not only the ones it witnesses

Every `[assembly: GoImplement<T, Iface>]` the converter writes comes from a **cast it converted** —
`convertToInterfaceType` records the pair it just emitted. Go satisfies an interface **structurally**,
so a package can implement one of its own interfaces completely and never write a conversion:
`encoding/binary` declares `type bigEndian struct{}` with the whole `ByteOrder` method set and exports
`var BigEndian bigEndian`, with no `var _ ByteOrder = BigEndian` anywhere. No cast, no record — so
`binary_package.bigEndian` was emitted as a partial struct that does **not** implement `ByteOrder`, and
every consumer minted its own `binary_bigEndianᴠByteOrder` adapter. This is the one place where *the
declaring assembly implements this pair* is TRUE in Go and FALSE in the emitted C#, and it is the root
the section above measured but did not close.

`recordSamePackageImplements` (`samePackageImplements.go`, called from `processConversion` after
the file visits and before `writePackageInfoFile`) walks the package scope and records the VALUE-form
pairs the package satisfies. `encoding/binary`'s metadata gains:

```csharp
// <InterfaceImplementations>
[assembly: GoImplement<bigEndian, AppendByteOrder>]
[assembly: GoImplement<bigEndian, ByteOrder>]
[assembly: GoImplement<littleEndian, AppendByteOrder>]
[assembly: GoImplement<littleEndian, ByteOrder>]
[assembly: GoImplement<nativeEndian, AppendByteOrder>]
[assembly: GoImplement<nativeEndian, ByteOrder>]
// </InterfaceImplementations>
```

and every consumer hands over the bare value, its own record and adapter gone with it — `debug/dwarf`'s
`d.Value.order = new binary_bigEndianᴠByteOrder(binary.BigEndian)` becomes
`d.Value.order = binary.BigEndian`, and `crypto/x509`'s
`crypto.SignerOpts signerOpts = new crypto_HashᴠSignerOpts(hashFunc)` becomes
`crypto.SignerOpts signerOpts = hashFunc`.

It records **through** `convertToInterfaceType` with an EMPTY expression — the record-only probe path
`convCompositeLit` / `convTypeAssertExpr` / `visitValueSpec` already use, since every emission arm is
gated on `exprResult != ""`. That is the whole design: a synthesized pair is composed, keyed and pruned
exactly as a real cast would compose, key and prune it, so no second naming path can drift from the cast
site's — the divergence that made the FOREIGN lookup miss for six weeks. Scope names arrive sorted, so
the record order is deterministic across runs.

**Five gates bound it, and each one is load-bearing.**

* **The interface is EXPORTED.** A record is a CROSS-ASSEMBLY contract — it exists so another
  assembly's cast can drop its local adapter — and no other assembly can name an unexported interface,
  so a record for one could never be consulted. The package's own casts already record what it needs
  internally.
* **The target's underlying is NOT a `*types.Signature`.** A named FUNC type is a C# delegate, which
  cannot be a partial struct, so `ImplementGenerator` emits an adapter CLASS for it; a consumer trusting
  THAT record hands a bare delegate to an interface slot (CS0029 — net/http's `HandlerFunc` → `ΔHandler`).
  This is the declaring-side half of `valueRecordRealizesAsPartialStruct` above.
* **Neither side is GENERIC.** A type argument cannot appear in an assembly-attribute type argument
  (CS0246) — the same exclusion `convertToInterfaceType`'s `targetIsOpenGeneric` makes.
* **Both sides are declared in a file this run CONVERTS.** A package scope holds every file's
  declarations, including build-constraint-excluded ones, and a record naming a type no emitted file
  declares is CS0246.
* **Every interface method is REALIZABLE by the generator** — it resolves on the type itself or
  through at most ONE embedded field (`types.LookupFieldOrMethod` index length ≤ 2).
  `ImplementGenerator` forwards a promoted member through a single embed hop and says so ("Go's
  promotion ambiguity rules make multi-embed satisfaction rare; extend when needed"), so a deeper
  promotion emits a forwarder through the WRONG hop: `CrossPkgUser`'s `rig` embeds
  `CrossPkgLib.Device`, which embeds `Sensor`, where `Label` lives, and
  `CrossPkgLib_package.Label(this.Device)` is CS1503 for want of `this.Device.Sensor`. Promotion
  through an embedded INTERFACE is the common shape this still admits (`sort`'s `reverse` embeds
  `Interface`; `debug/macho`'s segment types embed `LoadBytes`). The bound is deliberately
  CONSERVATIVE rather than a model of the generator's exact reach — it costs exactly two stdlib
  records (`net`'s `tcpConnWithoutReadFrom`/`tcpConnWithoutWriteTo`→`Conn`, whose `*TCPConn` hop the
  generator's `embedHopDeepPaths` arm can in fact follow), neither of which has a consumer, and
  withholding a speculative record is always safe: the consumer keeps the adapter it had before.

The gates bind only the SPECULATIVE recorder. A pair the source actually casts is DEMANDED and still
records at its cast site — promotion depth and all — so none of this narrows existing behavior.

The **POINTER** method set was deliberately out of scope here — `types.Implements(*T, Iface)` is the far
larger set, its records are adapter-class *existence* signals with a different trust rule, and it was owed
its own increment with its own measured footprint. That increment has since landed; see the next section.

**Footprint,** from a whole-stdlib A/B with both roots seeded (302/302 converted per side, 3690 files
compared CRLF-normalized): **68 files**, split evenly between metadata and code. **33 records** appear
across sixteen declaring packages; **31** go away — three dropped by the existing interface-inheritance
prune because a newly recorded pair subsumes one a cast had recorded (`flag`'s `textValue`→`Value` under
`Getter`, and `net`/`runtime`'s `errorString`→`error` under their own `ΔError`, which embeds it), and
twenty-eight consumer-local foreign records that existed only to generate an adapter. **89 adapter
constructions** disappear across 34 files — exactly the census the previous section predicted, pair for
pair: `binary_bigEndianᴠByteOrder` 43, `binary_littleEndianᴠByteOrder` 36, `color_PaletteᴠModel` 5,
`crypto_HashᴠSignerOpts` 5. Every changed consumer line is the same edit, the adapter construction
unwrapped to its argument; the full stdlib solution builds with 0 errors.

Most of the 33 new records have no consumer today — they are the rule stating what Go already says
(`sort`'s `reverse`→`Interface`, `image`'s `Rectangle`→`RGBA64Image`, `debug/macho`'s five `Load`
implementers, `io`'s `discard`→`StringWriter`), and they cost one assembly attribute each. Notably
ABSENT is `net/http`'s `HandlerFunc`→`ΔHandler`: the delegate gate holds on the corpus instance that
motivated it.

Guarded by the `SamePackageImplementNoWitness` behavioral test — a sibling `ledger` package that
declares an exported interface and value implementers and never converts one to the other, with its
negatives live rather than asserted (a named FUNC type, an unexported interface, a generic; the
pointer-only implementer was a fourth until the next section made it a positive). The pre-fix converter
records nothing and mints four adapters where the fixed one mints one; the delegate negative
(`ledger_MeterᴠMetric`) is byte-identical across the fix. `CrossPkgLib`/`CrossPkgUser` and
`ValueAdapterDynamicType` carry the same shape and re-baselined to the bare value. The realizability gate
is guarded by COMPILE rather than by a golden, and by the corpus case that found it: `CrossPkgUser`'s
`rig` is the depth-2 promotion, so dropping the gate puts a `GoImplement<rig, Labeled>` back and the suite
goes red on CS1503 in the generated forwarder.

### The POINTER method set records the same way, for a different contract

The section above closed the VALUE half of "the declaring assembly implements this pair is TRUE in Go and
FALSE in the emitted C#" and named the POINTER half as owed. This is that increment.

**Why it is not just "the same rule with a bigger set."** A value record and a pointer record are consumed
differently, and the difference decides both the gates and the failure mode. A VALUE record licenses an
IMPLICIT conversion: the declaring assembly's `partial struct T : Iface` means a consumer hands over the
bare value and names nothing. A POINTER record is an adapter-class EXISTENCE signal — `Pointer = true` is
exactly the shape `ImplementGenerator` realizes as `<T>ж<Iface>` — and the consumer CONSUMES it by NAME,
emitting `new pkg.TжIface(x)` where it would otherwise mint its own `pkg_TжIface`.

**Why cast-site sourcing is not good enough, stated as the bug it caused.** Every record the converter
writes comes from a cast it converted, so a pair's record lives or dies with the ONE body that happens to
witness it. `syscall`'s three `Sockaddr{Inet4,Inet6,Unix} → Sockaddr` pairs are witnessed by exactly one
method body, `(*RawSockaddrAny).Sockaddr`, and hand-owning that single function — which the blittable-
mirror work has every reason to want — silently dropped all three `(Pointer = true)` records, after which a
reconvert of `net` minted `syscall_SockaddrInet4жΔSockaddr` beside `syscall`'s own. Nothing failed to
compile; the L10 lane found it only because it re-converted a dependent and diffed. The pointer form's
symptom is milder than the value form's `%v`-over-the-wrapper crash — both adapters wrap the same box and
compare equal — but it is a NON-DETERMINISTIC dynamic type: each adapter's module initializer calls
`AdapterRegistry.Register` first-wins, so which class a type assert re-wraps into follows assembly load
order. Sourcing the record from the METHOD SET makes the pair independent of which bodies a run converts,
which is the root fix rather than a rule about what may be hand-owned.

`recordSamePackageImplements` (the renamed `recordSamePackageValueImplements`) therefore asks BOTH
questions of every candidate and records both forms. The pointer set is a SUPERSET of the value set, so a
value-satisfied pair is recorded twice, and that is deliberate: Go's `T` and `*T` are two dynamic types,
realized as the partial struct and the adapter respectively, and dropping the pointer record for such a
pair leaves a consumer's `var i Iface = &t` with nothing to reference.

**The gates are the same five, plus one.** The added gate is the trust rule made mechanical:

* **BOTH sides are EXPORTED** (`pointerRecordIsPubliclyRealizable`). `ImplementGenerator` scopes the
  adapter class `public` only when the struct and the interface are each public, `internal` otherwise. A
  record is a cross-assembly contract, and this form's contract is *"this class exists and you may name
  it"* — so a record naming an unexported participant advertises a class no consumer can reference
  (CS0122), an existence signal that is a lie. The value form needs only the interface gate because its
  contract is realized by a conversion that names nothing, which is why the two rules differ here and only
  here.

The realizability gate is not merely re-asked of `*T` — it is TIGHTENED, and that is the second place
the two forms genuinely differ. `generatorCanForwardPointerMethodSet` requires every interface method to
resolve **DIRECTLY** on the type (index length 1), admitting no promotion at all, where the value bound
admits one embed hop. A partial struct's explicit implementation resolves a promoted member the way the
converter's own call sites do; the ж adapter does not. Its promoted-member arms are keyed on embedded
**POINTER** fields (`GetEmbeddedPointerHopNames`), and with exactly one such field the single-hop arm
takes every unbound member *unconditionally* — which the generator says outright, and is right to, because
for a DEMANDED record that member's promotion is what type-checked the cast. For a SPECULATIVE record it
is not: the member's true source may be a different embed entirely.

`StructPointerPromotionWithInterface`'s `MyCustomError` is the corpus instance, and the `go2cs.slnx` build
found it rather than reasoning did. It embeds BOTH the `Abser` interface and `*MyError`; `Abs` is promoted
from the INTERFACE, but the adapter's lone pointer embed is `*MyError`, so the generated forwarder bound
`Abs` against `MyError` — where the only candidate in scope was `time.Abs(Duration)`: **CS1929**, in a
generated file, naming `time` from a test about struct promotion. **Depth is not the discriminator** (that
promotion is index length 2, which the value bound admits); the KIND of hop is, and modelling the
generator's exact hop selection inside the converter would duplicate its internals in a second place —
the very drift this recorder's design exists to prevent. So the bound is conservative in the same spirit
as the value one and safe in the same way: withholding a speculative record leaves the consumer with the
local adapter it already had. A pair the source actually CASTS is untouched and keeps the full promotion
support the generator was built for, which is what that behavioral test guards. A named FUNC type is
excluded before either question is asked, as before.

**Footprint,** from a whole-stdlib A/B with both roots seeded (304/304 converted per side; marker gate
54 marked files / 43 `*_impl.cs` companions / **0 violations** on both roots): **75 files** — 35
`package_info.cs` and 40 code — with **0** `.csproj` and **0** `README.md` moved. **184 records appear**
across 22 declaring packages (`go/ast` 96, `image` 17, `io` 14, `image/color` 12, `database/sql` 8,
`net` 7, `math/rand/v2` 4, `sort` 3, …) and **117 go away**, every one a consumer-local duplicate the
declaring assembly now owns: `go/parser` 49, `go/types` 28, `go/doc` 8, `go/printer` 5 (all of them
`go/ast` node types), `net/http` 4, the five `debug/*` + `internal/xcoff` readers' `io.SectionReader`
pairs, `net/http/httputil`'s `io.Pipe{Reader,Writer}`, and one each for `sync.Mutex`→`Locker`,
`image/color.RGBA64`→`Color` and `parse.BranchNode`→`Node`. Net corpus movement is **+67** pointer
records (1,071 → 1,138), not the 548 the deferral's raw pair count suggested — most of that set was
already recorded from cast sites, and the gates take the rest.

**318 adapter constructions** are repointed across 40 files, every changed line the same edit —
`new pkg_TжIface(x)` becomes `new pkg.TжIface(x)` — which is the second-identity elimination measured:
318 sites that used to name a locally minted duplicate now name the declaring assembly's one adapter.
There is no third family; a classifier over the whole diff reports **zero** unclassified added lines.

Guarded by `SamePackageImplementNoWitness`, whose `*Tally → Metric` pair moved from negative to positive
(the consumer now references `ledger.TallyжMetric` instead of minting `ledger_TallyжMetric`) and which
gained the negative this gate needs — `tick`, an UNEXPORTED target whose pointer set implements the
exported interface, kept live through `ledger.Count` and absent from `ledger`'s metadata. Also guarded by
`ForeignPointerImplementSuppression`, where `Lone` — a pair `tone` satisfies and never casts — flipped the
same way and is now that test's proof that a record needs no witnessing cast, while its `shade.Level`
negative (a same-SIMPLE-named interface in another package, which must keep its local adapter) is
byte-identical across the change. Unit-guarded by `TestPointerRecordIsPubliclyRealizable`,
`TestGeneratorCanForwardMethodSetDepthBound` and `TestPointerMethodSetSubsumesValueMethodSet`.

**Acceptance witness — the L10 probe, re-run to prove the absence of what it once measured.** With
`RawSockaddrAny.Sockaddr` suppressed through `manualConversionFuncs` (a scratch build on each side, so
the only variable is the recorder), `syscall` and `net` were reconverted into seeded roots:

| | `syscall`'s `(Pointer = true)` Sockaddr records | `net`'s six construction sites |
|---|---|---|
| pre-increment converter + suppression | **absent** (all three) | `new syscall_SockaddrInet4жΔSockaddr(…)` — locally minted duplicates |
| post-increment converter + suppression | **all three present** | `new syscall.SockaddrInet4жΔSockaddr(…)` — syscall's own adapter |

The pre-increment row is the regression exactly as L10 measured it; the post-increment row is the same
probe finding nothing to report. That is what makes the hand-own safe rather than merely discouraged.

### Standard-library solution file (`.slnx`)

A whole-standard-library run (`go2cs -stdlib`) also emits a Visual Studio solution — **`go2cs-stdlib.slnx`** — at the output root (`-go2cspath`), so the freshly converted stdlib is openable / buildable as **one unit** immediately after a run, rather than depending on a hand-maintained solution that drifts. It is the auto-generated counterpart of the committed `src/go2cs-stdlib.slnx`, and its XML mirrors the format of `src/go2cs.slnx` (a `<Configurations>` block plus `<Folder>`/`<Project>` entries, CRLF line endings, no BOM). It references:

* every converted stdlib project it emitted (`core/<pkg>/<projectName>.csproj`, grouped under a `/core/` folder),
* any per-package **test** projects (`*_test.csproj`, grouped under a `/tests/` folder — inert until Phase 4 emits them, and the folder is omitted entirely when there are none),
* the shared **`golib`** runtime (`core/golib/golib.csproj`), and
* the **`go2cs-gen`** source-generator/analyzer project (`gen/go2cs-gen/go2cs-gen.csproj`, under a `/generators/` folder).

The stdlib project list is gathered by walking the emitted `core/` output tree (so it also picks up future test projects with no code change), and every project is emitted in stable **alphabetical** order for deterministic output. All paths are **solution-relative** (forward slashes) so the generated solution is portable — no absolute, machine-specific paths. The `golib` and `go2cs-gen` references use the same `core\golib` / `gen\go2cs-gen` layout the converted `.csproj` files already assume via `$(go2csPath)` (which resolves to `$(SolutionDir)`), so the solution locates them wherever those csproj references already resolve. The file is only rewritten when its content changes, so repeated runs are a no-op.

### Recurse per-project solution file (`.slnx`)

A recursive end-user run (`go2cs -recurse`) instead emits **one `.slnx` next to every converted `.csproj`** (`ModuleConverter.generatePerProjectSolutions`), each over that anchor project plus its **transitive converted dependencies** + `golib` + the analyzer — no stdlib listed (the stdlib is *referenced* via `$(go2csPath)core`, pre-staged by `deploy-core`). Building the app's own per-project solution thus builds the app and its whole converted dependency closure in one `dotnet build`, without the ~300-project stdlib solution. The anchor project is marked the Visual Studio default startup project (`DefaultStartup="true"`).

Projects are grouped into **three top-level solution folders that mirror the `%GOPATH%` layout**, emitted in an **enforced, deliberately non-alphabetic order**:

* **`/src/`** — the project(s) being converted (the app's own main-module packages),
* **`/pkg/`** — their converted dependency packages (module-cache or `replace` third-party), then
* **`/core/`** — the go2cs runtime/generator projects (`golib`, `go2cs-gen`).

Each member is placed by **import path** (`isMainModulePackage` — the same rule that routes a package's output to `src\` vs. `pkg\`), so the solution folders agree with the on-disk parallel tree regardless of the `.slnx`-relative path shape. An **empty folder is omitted** — a dependency's own per-project solution has no `src` package — mirroring how the stdlib solution drops its `/tests/` folder when empty. Because the three folder names are unique leaves, no folder `Id` attribute is emitted (unlike the namespace-nested stdlib solution, whose duplicate leaves like `crypto`/`internal` require the hashed `folderID`). Paths are solution-relative forward slashes, CRLF line endings, no BOM; the file is rewritten only when its content changes. Rendered by `buildRecurseSolutionXML` (`solutionGenerator.go`), guarded by `TestBuildRecurseSolutionXML`, `TestBuildRecurseSolutionXMLSkipsEmptyFolders`, and the folder-order assertions in the `TestRecurseSyntheticModule` integration test.

## Package-Level Variable Initialization Order

Go initializes package-level variables in **dependency order** (spec: "within a package, package-level variable initialization proceeds stepwise, each step selecting the earliest variable … that has no dependencies on uninitialized variables"), where dependencies are resolved **through function calls and function literals** referenced by the initializer. The default conversion emits a package var as a C# static field with an initializer — but C# executes static field initializers in textual order **within** one file of the partial package class and in an **undefined** order **across** files. Three dependency shapes therefore break at runtime while compiling cleanly (the Phase-3 → Phase-4 distinction in miniature):

1. **Cross-file:** syscall's `var procSetFilePointerEx = modkernel32.NewProc("SetFilePointerEx")` (syscall_windows.go) reads `modkernel32` declared in zsyscall_windows.go — with the wrong file order the receiver box is nil (NullReference in the type initializer, the first Phase-4 crash of any program importing `os`).
2. **Cross-file through a function:** syscall's `var Stdin = getStdHandle(STD_INPUT_HANDLE)` — the initializer calls a package *function* whose body reads zsyscall_windows.go's `procGetStdHandle`. Same failure, invisible to a direct-reference scan; dependencies must be resolved transitively through same-package function bodies (and func-literal bodies — an IIFE initializer executes at init time), exactly as Go's own analysis does.
3. **Same-file forward reference:** `var first = base + 1` declared above `var base = 41` — C# reads `base`'s zero value (silently wrong value, no crash).

**The conversion:** `collectMovedInitVars` (converter: `initOrderOperations.go`) walks `types.Info.InitOrder` — Go's authoritative dependency-sorted initializer list — resolving each initializer's same-package var dependencies transitively through package function/method bodies. An initializer moves when a dependency is cross-file, a same-file forward reference, or **itself moved** (a moved var is only assigned in the ctor, so its dependents can no longer stay field initializers regardless of layout). A moved var is emitted as a **bare field** plus a tiny **init method beside it in its home file** (so the rendered expression keeps that file's using aliases), and a generated `package_init.cs` supplies the package class's **static constructor**, calling the methods in InitOrder:

```go
// syscall_windows.go
var procSetFilePointerEx = modkernel32.NewProc("SetFilePointerEx")
```

```csharp
// syscall_windows.cs
internal static ж<LazyProc> procSetFilePointerEx;
internal static void initᴛprocSetFilePointerEx() { procSetFilePointerEx = modkernel32.NewProc("SetFilePointerEx"u8); }

// package_init.cs (generated)
partial class syscall_package {
    static syscall_package() {
        initᴛprocSetFilePointerEx();
        // … every relocated initializer, in types.Info.InitOrder …
    }
}
```

This is correct by C#'s own initialization guarantees: **all** static field initializers (every partial-class file) run **before** the static-constructor body, so every non-relocated dependency is already initialized when the ctor runs; the ctor then applies the relocated initializers in Go's order. Vars with no order hazard (the overwhelming majority — only 25 of the 302 stdlib packages relocate anything) keep their readable inline form. Cross-**package** order needs no handling: accessing another package's static field triggers that type's initialization first (.NET guarantees), matching Go's imported-packages-first rule. Adding an explicit static ctor also removes `beforefieldinit` from the package class, giving it *precise* initialization semantics.

Notes: a **blank** (`_`) initializer never relocates (its value is unreadable, so its order is immaterial — it still runs as a field initializer for its side effect); the `initᴛ` method name composes the TempVarMarker so it cannot collide with any Go identifier; an **addressed** global relocates as a default-valued heap box whose ctor assignment writes through the ref property into the same box; a **tuple-deconstructing** spec relocates as one unit (see the next subsection). The one remaining warn-and-stay-inline fallback is a moved PLAIN var whose initializer carries a multi-value hoisted inner call (`globalDeclHoist` — the `template.Must(template.New(…).Parse(…))` spread shape); the whole-corpus census found zero flagged occurrences of it. Guarded by the `PackageVarInitOrder` behavioral test (all three hazard shapes plus IIFE and moved-dependency closure, output-compared vs Go).

### A TUPLE-deconstructing package var relocates as ONE unit

A package-level `var a, b = f()` — one multi-value call deconstructed across the names — is a single initialization step in Go: `types.Info.InitOrder` carries **one entry for the whole spec** with every name in its `Lhs`, so `collectMovedInitVars` flags all of a spec's non-blank names together under one shared ordinal, and the emission registers **one `initᴛ` method per spec** at that ordinal — `writeOrderedInitCalls` needs no new bookkeeping. Until 2026-08-11 this path *refused* to relocate ("unsupported for tuple specs", warn and leave inline), on a "no stdlib occurrence" premise the census falsified: `crypto/internal/edwards25519`'s `var identity, _ = new(Point).SetBytes(…)` and `var generator, _ = …` reach `feOne` and `d` — declared *later* in the same file — through the package's own `(*Point).SetBytes`, so the inline field initializers ran first, `identity` read a null `feOne`, `field.Subtract` null-dereferenced, and the package cctor threw before any test ran (the corpus's only whole-package casualty: 0 of 55 verdicts, restored to 52 of 55 by the relocation — the three residuals are separate pre-existing roots). Census: exactly two production occurrences on Windows (both edwards25519) and two latent on darwin (`os`'s `executable_darwin.go`); full detail in [`docs/phase4/FINDING-init-order-tuple-specs.md`](phase4/FINDING-init-order-tuple-specs.md).

Two emission sub-shapes (`writeMovedPackageTupleVarSpec`, `visitValueSpec.go`), both turning every name into a **bare field**:

**One non-blank name** (the edwards25519 shape): the method assigns its component directly from the once-run call; blank siblings keep their uninitialized `_ᴛNʗ` fields — the call now runs in the ctor, so no blank ever carries it (from the `InitOrderTupleSpecs` golden, `var single, _ = makeGreeting()`):

```csharp
internal static @string single;
internal static error _ᴛ1ʗ;
internal static void initᴛsingle() { single = makeGreeting().Item1; }
```

**Two or more non-blank names** (darwin `os`'s `var initCwd, initCwdErr = Getwd()` shape; golden: `var cwd, cwdErr = fakeGetwd()`): the method evaluates the call **once into a method-local** and assigns each non-blank component from it — the inline path's hidden static tuple holder is unnecessary, because the method body itself sequences the call before its reads. The local reuses the holder's minted `tupleᴛNʗ` name shape so it cannot collide with anything the rendered call expression references:

```csharp
internal static @string cwd;
internal static error cwdErr;
internal static void initᴛcwd() { var tupleᴛ1ʗ = fakeGetwd(); cwd = tupleᴛ1ʗ.Item1; cwdErr = tupleᴛ1ʗ.Item2; }
```

An **all-blank** spec (`var _, _ = f()`) never relocates — blanks are excluded from the moved set, so it keeps the inline emission where the first blank's field initializer carries the call for its side effect. An **addressed** name relocates as the default-valued heap box with the method assignment writing through the ref property, exactly like the plain path. A blank in the **middle** of a spec simply drops out of the assignment list (the golden's `var head, _, tail = makeTrio()` assigns `.Item1` and `.Item3`). (Guarded by the `InitOrderTupleSpecs` behavioral test — both sub-shapes, the mid-spec blank, a plain var chained onto a moved tuple var, an addressed moved tuple var, and an order-safe inline control, output-compared vs Go — and by the converter unit test `TestPackageTupleVarSpecInitOrderRelocation`, which additionally pins that the refusal warning no longer fires and that an order-safe spec keeps the inline holder emission.)

### Test-variant (`-tests`) initializers relocate too — through the erasable static-ctor hook

The same pass runs in the `-tests` conversion driver (the three-drivers rule): a `_test.go` package-level var whose initializer reads a var declared later in the file, in another file, or in the production package cross-file has exactly the same hazard — internal/fmtsort's `compareTests` reads `chans`/`ints` declared 170 lines below it, so every test died in the class initializer on the default slice; encoding/gob's `basicTypes` table reads production `type.go` vars cross-file. The relocated **emission** is unchanged (bare field + `initᴛ<name>` method beside the declaration); only the **ordered-constructor site** differs by variant, because a C# class gets ONE static constructor across all partial parts:

* **External variant** (`<pkg>_test` — its own `<pkg>_test_package` class): a generated `package_init_external_test.cs` supplies the class's static constructor directly, exactly like `package_init.cs`.
* **Internal variant** (test files join the production `<pkg>_package` class): when the production `package_init.cs` exists it already owns the single static-ctor slot, so under `-tests` its constructor ends with a call to an **erasable classic partial method** — `static partial void initᴛᴛtests();` — and the test conversion emits `package_init_internal_test.cs` implementing it with the test-side relocations. Unimplemented (the production compile set excludes `*_test.cs`), C# erases both the declaration and the call, so the production assembly is untouched at runtime. Test relocations therefore run after every production relocation — semantically safe, since a production initializer can never depend on a test var. When the production package relocated nothing, the internal test variant claims the static ctor itself.

Two hard-won rules ride along: the hook name doubles the TempVarMarker (`init` + `ᴛᴛ` + `tests`, `Symbols.PackageTestInitHookMethod`) so no relocated var's `initᴛ<name>` method can collide; and go2cs-gen's **PartialStubGenerator must never stub the hook** — its whole design is C# erasure when unimplemented, and the generator's `NotImplementedException` stub for "asm/cgo" bodyless partials detonated encoding/gob's static ctor for every consumer of the production assembly (go/token's serialize path) until the generator learned to skip it (guarded by `PartialStubGeneratorTests.StubsAsmPartialsButNeverTheTestInitHook`). The hook emission is `-tests`-gated, so a plain `-stdlib`/behavioral conversion is byte-identical — like the IP-4 csproj test-artifact exclusions, the production-file difference is intended `-tests` output, not drift. The relocated test files themselves ride the banked suites (fmtsort's cctor is the operational guard). The hoisted-literal interplay keeps its conservative setting (`initOrderRelocated=false` for the real test emission run): suppressing test-file hoists in initializer-reachable functions is a pure allocation pessimization, never a correctness risk, and flipping it would drift every banked `*_test.cs`.

### A CONSTANT emitted as an initialized FIELD is an initialization dependency too

Go constants are compile-time values with no initialization order at all, so `types.Info.InitOrder` never mentions them — and for almost every constant that stays true in C#, because a constant C# cannot declare `const` is emitted as a **get-only property** rather than a field (see [Constant Values](#a-constant-c-cannot-declare-const-is-a-get-only-property-not-a-static-readonly-field)). A property re-evaluates at each read, so it can never be observed at its zero value, whatever the declaration order.

**Two forms cannot be properties**, because re-evaluating them would rebuild an allocation on every read: a **string** constant (`@string`, or a `[GoType("@string")]` wrapper such as `const labelPipe label = "pipe"`, whose `u8` literal the string-literal arc deliberately hoists to a single allocation) and a **GoBigConst** constant (a `BigInteger.Parse`). Those two stay `static readonly` **fields with initializers**, and therefore carry exactly the cross-part field-initializer ordering hazard a package *var* has. The relocation analysis has to supply that dependency edge itself, because Go's own analysis has no reason to model it:

```go
// server.go — a plain string const, so a `static readonly @string` FIELD
const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
```
```go
// header.go — sorts BEFORE server.go in the compile set
var timeFormats = []string{TimeFormat, time.RFC850, time.ANSIC}
```

Read before its initializer runs, `TimeFormat` is the empty `@string`, and `net/http`'s date parsing silently loses its primary format. `collectRefs` records package-scope `*types.Const` references whose emission is an initialized field (`constEmittedAsInitializedField`, keyed off the constant's **value kind** — string always; int, float and complex only where the magnitude escapes to `GoBigConst`), resolved transitively through function bodies on the same memoized graph the var closure uses. A const dependency forces the move under the same two rules a var dependency does: declared in a different file, or later in the same file. A const is of course never itself *relocatable*, so only its declaration site matters.

The emission side needed the matching half. Relocation lived only in the **non-constant** initializer arm (`tv.Value == nil`), yet a Go-**constant**-valued initializer is just as order-sensitive in C#: Go folds the value at compile time, but the conversion deliberately keeps the *source expression* for readability, and that expression still reads the field:

```go
// registry.go
type label string
const labelPipe label = "pipe"
```
```go
// entries.go — compiled first; the initializer folds to the constant "pipe!" in Go
var pipeLabel = string(labelPipe) + "!"
```
```csharp
// entries.cs — relocated, because the rendered expression READS the labelPipe field
internal static @string pipeLabel;
internal static void initᴛpipeLabel() { pipeLabel = ((@string)labelPipe) + "!"u8; }
```

**Where the edge came from, and what it kept.** The defect that exposed it was `regexp/syntax`'s `opNames` — a 19-entry table index-keyed by the `Op` constants declared in a later file, every key read as zero, the whole table collapsing to one slot so `Parse("a").Dump()` printed the numeric fallback `op3{a}` where Go prints `lit{a}`. `Op` is a named `uint8`, so the property rule now removes *that* hazard at the source, and an over-broad predicate naming every non-`const` form would draw an edge for a shape that no longer has one. Scoped to the residue, the edge's corpus footprint is **two packages**: `internal/buildcfg` relocates five initializers (`GOARCH`, `GOOS`, `GO386`, `GO_LDSO`, `Version` — each `envOr("…", default…)` over a string const in `zbootstrap.go`) and `net/http` relocates `timeFormats`. Both were latent zero-value const reads that compiled cleanly.

Guarded by the `PackageVarInitOrder` behavioral test, which carries both halves: neutering the predicate collapses `pipeLabel` onto an empty `@string` (stdout mismatch), and neutering the property rule allocates a fixed-array field at length 0 and panics on the first indexed write (exit code 2). Neither is redundant.

## Compiled Library versus Source Code

One big difference between Go and many other languages is the notion of _source availability_. Traditionally programming languages have depended on using a pre-compiled library — both to avoid recompiling the library and to protect source as intellectual property. Go was born in an era of faster computing and prolific open source; it relies on having access to all source at compile time, including library code. Go takes advantage of this to make interesting optimizations, especially around when a structure escapes the stack to the heap. Keeping structures off the heap means they do not need to be tracked for garbage collection, and the Go compiler manages this automatically. The interesting consequence is that, for a given use of a library as source, an application structure may or may not escape to the heap depending on how it flows through the code — an optimization only possible when all source is compiled together.

Because this is a complex optimization, the converter currently assumes structures can escape to the heap except in the simplest-to-detect cases (see [Pointers](#pointers)). A future option could distinguish optimizations targeted at a compiled library (very safe escape analysis) versus a standalone application (more aggressive). Already-converted packages **are** referenced as compiled libraries — the consumption model most C# developers are accustomed to — which takes two pieces, both in place: `-recurse=nuget` maps each imported Go package to its published `go.<pkg>` package reference, and the exported metadata a consumer would otherwise scrape out of the dependency's converted source travels with the converter (see [A NuGet-referenced standard library carries its exported metadata IN THE CONVERTER](#a-nuget-referenced-standard-library-carries-its-exported-metadata-in-the-converter)).

## Constant Values
Go constants hold arbitrary-precision literals with expression support, and assignment of a constant to a variable happens at compile time. The converter preserves the constant value (and, in a comment, the original expression). A *typed* Go constant is emitted with its concrete C# type, e.g.:

```csharp
public const nint MaxRetries = 3;
```

An *untyped* Go constant is emitted using a golib "untyped" wrapper type — `UntypedInt`, `UntypedFloat`, or `UntypedComplex` — so it can hold a value that does not fit a single primitive and can implicitly adapt to whatever numeric type its use site requires (mirroring how an untyped Go constant takes its type from context). A `[GoType]` struct is not a legal C# constant type, so the declaration is a get-only **property** rather than a field (see the next subsection for why):

```csharp
internal static UntypedInt win => 100;
public static UntypedInt N => /* 11 + 1 */ 12;
```

### A constant C# cannot declare `const` is a get-only PROPERTY, not a `static readonly` field

A Go constant has **no initialization**: it is a compile-time value usable from anywhere in the
package regardless of declaration order. Whenever C# *can* say `const` (a primitive-typed const) that
property is preserved for free. It cannot for the wrapper types above, nor for a named type, a
`uintptr`, or a complex — and a `static readonly` **field** reintroduces exactly the initialization
Go does not have. C# runs static field initializers in class-**textual** order (across a partial
class, in `<Compile>`-item order), so a package-level variable declared *ahead* of the constant read
it as the type's DEFAULT — silently, with no diagnostic:

```go
var fixedHuffmanDecoder huffmanDecoder    // compress/flate/inflate.go — declared FIRST
...
const huffmanNumChunks = 1 << huffmanChunkBits   // …the const it transitively needs comes LATER
type huffmanDecoder struct { chunks [huffmanNumChunks]uint32 }
```

As a field, `huffmanNumChunks` was still `0` when that variable's `new huffmanDecoder()` ran its
`chunks = new(huffmanNumChunks)` field initializer, so the decode table was allocated at length **0**
where Go says 512: `init` filled nothing (its `for off < len(h.chunks)` loop never ran) and every
later `chunks[i]` read panicked with `index out of range [281] with length 0`. The same trap zeroed
`maxNumLit` for `fixedLiteralEncoding`'s initializer *across files*, emptying the fixed Huffman code
table. Both are silent-correctness defects that compiled clean, and both took down every dependent
package (`compress/gzip`, `compress/zlib`).

A get-only property carries no initialization at all, so declaration order cannot be observed and the
JIT folds the literal at each use — Go's semantics exactly:

```csharp
internal static UntypedInt huffmanNumChunks => /* 1 << huffmanChunkBits */ 512;
public static ΔKind Uintptr => 12;                        // named-type const
internal static uintptr MaxUintptr => unchecked((uintptr)18446744073709551615);
```

**RESIDUE — the two ALLOCATING const forms stay `static readonly` fields**: `@string` (whose `u8`
literal the string-literal arc deliberately hoists to a single allocation) and `GoBigConst` (a
`BigInteger.Parse`). A property would rebuild their value on every read. Neither can serve as an
array length, so neither reproduces the failure class above — but a package-level variable
initializer that reads one *before* its declaration point still would, so the residue is handed to
the [initialization-order pass](#a-constant-emitted-as-an-initialized-field-is-an-initialization-dependency-too),
which draws a relocation edge for exactly these two forms and nothing else.

The two rules divide the problem cleanly and neither is redundant: the property form takes every
numeric, named-numeric, `uintptr`, complex and untyped constant *out* of the ordering problem, and
the relocation edge orders the string/`GoBigConst` residue that has to stay in it. Guarded together
by the `PackageVarInitOrder` behavioral test (cross-file constants consumed by earlier-declared
vars — directly, through a struct's fixed-array field initializer, and through a constant-folded
initializer over a named-string const).

**Wrapper conversions are VALUE conversions in every direction.** `UntypedInt` stores its payload as
`int64` bits (so a `ulong`-range literal like `9223372036854775808` round-trips through the same 8 bytes),
and its float/complex operators originally *bit-reinterpreted* that payload — `var fl float64 = m` (m an
untyped `3`) produced `1.5e-323`, the denormal double whose bit pattern is 3, instead of `3`. The
float32/float64/complex64/complex128 operators now convert by **value**, like every integer-direction
operator always did. Because the payload can be unsigned-flavored (a beyond-int64 `ulong` literal such as
`1 << 63` uses the `uint64` constructor — bits identical to a signed `-9223372036854775808`), the wrapper
carries a payload-kind discriminator so a float conversion keeps the uint64 magnitude and `ToString()`
prints it unsigned. (Guarded by the `UntypedIntFloatContexts` behavioral test — one local const used in
both int and float contexts, the `1<<63` unsigned payload, complex contexts via `real`/`imag`, and a
negative payload, values verified vs Go.)

**COMPARISON is a value comparison too, across payload kinds.** The `(payload, unsigned-flavour)` pair
denotes a mathematical integer, and `<`/`<=`/`>`/`>=`/equality must order over that VALUE — but they
compared the raw `int64` bits, so a `uint64` at or above 2^63 (which reads as a NEGATIVE `int64`)
answered `u < someSmallConst` **TRUE**. That is a silent wrong answer for the whole
`if fastSmalls && i < nSmalls` idiom the standard library uses: `strconv.FormatUint(9223372036854775808, 10)`
took the small-integer fast path, `small()` truncated the negative index, and the result was `"0"`
(`TestUitoa`); the varlen sibling threw on the truncated slice bound (`TestFormatUintVarlen`). The
operators now route through one `Compare` helper: same flavour compares at that flavour, and mixed
flavours put a NEGATIVE signed payload below every unsigned payload (otherwise both are non-negative
and the unsigned reading of each is exact). Only a payload at or above 2^63 changes answer — every
signed-only or in-`int64`-range comparison is bit-for-bit what it was. (Guarded by the
`BigUntypedConstComparison` behavioral test's wrapper arm — `0`/`999`/`1000`/`1001`/`1<<62`/`1<<63`/
`1<<64-1` against a wrapper const across all six relations, the signed counter-cases, and the inlined
`FormatUint` fast-path predicate, output-compared vs Go; the test FAILS `[Output]` without the fix.)

**The wrapper carries 64-bit shift operators so a still-wrapped untyped shift keeps Go's width.** `UntypedInt` defines `<<` and `>>` (an `int` count) returning `UntypedInt`, shifting the `int64` payload. Without them, a wrapper-typed untyped constant shifted by a **non-constant** count bound through the implicit `UntypedInt → int` conversion — a 32-bit `int` shift, which both masks the count to its low 5 bits and truncates the payload to 32 bits — whereas Go shifts at the type the untyped operand assumes from context (typically the enclosing `uint64`). `math.Frexp` corrupted on exactly this: `x |= uint64((-1 + bias) << shift)` (`bias` a package-level `UntypedInt` const, so the compound `-1 + bias` stays `UntypedInt`; `shift` a runtime `int`, up to 52) emitted `((-1 + bias) << (int)(shift))` and computed `1022 << (52 & 31)` = `1022 << 20` instead of `1022 << 52`, scrambling the exponent field of the assembled `float64`. The operators keep the shift a 64-bit `long << int` returning `UntypedInt`, so it composes with the surrounding `(uint64)` conversion and reproduces Go's value. Note this fires only for a **compound** untyped operand (`bias - 1`): a bare-identifier operand (`bias << n`) is cast to the shift's context result type at the use site first, so it never reaches the wrapper operator. (Guarded by the `UntypedIntWideShift` behavioral test — package-level `UntypedInt` consts `bias`/`bits`, the compound `bias - 1` left-shifted and `bits - 1` right-shifted by runtime counts 52/40/33 (all > 31, so a 32-bit narrowing would mask them), values verified vs Go.)

**Float constant values are emitted exactly.** The emitted value of a float-kind constant declaration is never `go/constant`'s `Value.String()` — that is a *shortened* human-readable form (~6 significant digits), and using it silently truncated the **compiled** value while the exact literal survived only in the `/* … */` comment (math `cbrt`'s `C = /* 5.42857142857142815906e-01 */ 0.542857`). The emission prefers the Go **source literal verbatim** when it is also valid C# syntax — decimal floats including exponents and `_` digit separators overlap C# exactly, and a unary-minus form (`-7.05306122448979611050e-01`) carries its sign — which also elides the now-redundant original-expression comment (the emitted value *is* the original). When no single valid literal exists — a folded constant expression (`19.0 / 35.0`), or a Go-only literal form (hex float `0x1p-2`, trailing-dot `5.`) — the value emits as the **shortest round-trip** form (`strconv.FormatFloat 'g'/-1`) of the constant converted at the declaration's width (bitSize 32 for a `float32`-typed const, so the `f`-suffixed single parses with the same one rounding Go applies; 64 otherwise):

```go
const (
    C              = 5.42857142857142815906e-01 // 19/35 = 0x3FE15F15F15F15F1
    D              = -7.05306122448979611050e-01
    folded         = 19.0 / 35.0
)
```
```csharp
internal static readonly UntypedFloat C = 5.42857142857142815906e-01;
internal static readonly UntypedFloat D = -7.05306122448979611050e-01;
internal static readonly UntypedFloat folded = /* 19.0 / 35.0 */ 0.5428571428571428;
```

A beyond-float64 value still routes to the `GoBigConst` (BigInteger) overflow path unchanged. (Guarded by the `UntypedConstDefine` behavioral test — package-level and function-local high-precision consts printed and compared against Go, which fails with the truncated `0.542857` emission; `SortArrayType` additionally locks the verbatim forms `1.0f`/`3.14e100`.)

**A FUNCTION-BODY float const referencing a named untyped-float const folds the same way (2026-07-18).** The exact-emission above is a *declaration* rule; the identical double-rounding hazard exists for a compile-time float constant computed in a FUNCTION body that references a named untyped-float const (`math.Pi`, `math.Ln10` — each emitted as a golib `UntypedFloat` wrapper already rounded to float64). Left as runtime C# arithmetic, `float64(100000 * Pi)` becomes `(float64)(100000D * Pi)` and rounds a SECOND time — 314159.2653589793, a ULP below Go's single arbitrary-precision fold 314159.26535897935 (`math`'s `TestLargeCos` was fed a −1-ULP argument; the trig algorithm itself is bit-exact), and `1 / Ln10` in `Log(x) * (1/Ln10)` the same. `foldedNamedFloatConstLiteral` emits the Go-folded value as a `/* <expr> */ <literal>` at the RESOLVED float width, at two sites: a `float64(…)`/`float32(…)` type CONVERSION (`convCallExpr`, restricted to a *basic* float target so a named float type keeps its `[GoType]` wrapper path) and a computed-const OPERAND being cast to a concrete float in a binary expression (`convBinaryExprCore`, reusing the sibling's resolved type). The width is honored exactly — a float32 target rounds the exact constant STRAIGHT to float32 (`exactFloatText`'s `constant.Float32Val`), never through a float64 intermediate, which would double-round *differently* than Go's single round-to-float32. Gated to a COMPUTED float const that references a named untyped const: a bare named-const reference already renders as a single-rounded wrapper, and a pure-literal float const (`1.5 * 2.0`, no named ref) computes exactly in C# double and keeps its readable operator form. Cleared `math`'s `TestLargeCos/Sin/Tan/Sincos` + `TestLog10` (68 → 73 / 77) by folding 34 sites across the sin/tan/atan/erf/jN/lgamma/pow families; the emission is a no-op on the behavioral corpus (nothing there exercises the pattern). (Guarded by the `NamedConstFloatFold` behavioral test — a `float64(100000*myPi)` conversion, its float32 counterpart, and a `1/myLn10` typed-float64 operand, output-compared vs Go.)

**Function-local untyped constants TIGHTEN to their single concrete use type.** A per-function analysis pass (`performUntypedConstAnalysis`) resolves every use of each function-local untyped numeric constant through go/types: when ALL uses record the SAME concrete basic type — and none participates in constant folding — the declaration emits at that type (with C#'s `const` keyword where legal: the primitive aliases; native-int/`uintptr` values fall to the existing `static readonly`/`unchecked` demotions), and every cast the wrapper made necessary (the bitwise/arith operand casts, the `append`/deferred-call element casts, the 32-bit-and-wider shift retype) is skipped as redundant. One cast is **kept because it is value-changing, not wrapper-driven**: a tightened const of a *sub-int32* type (`int8`/`int16`/`uint8`/`uint16`) as the LEFT operand of a non-constant shift keeps the width retype — C# promotes a narrow shifted operand to `int`, so without `(byte)(cb << (int)(k))` Go's wraparound at the declared width is lost (`const cb = 200; b + cb<<k` → Go wraps `200<<1` to 144, byte width, result 145; the promoted C# shift computes 400 → 401). The emitted code otherwise reads like the Go source — math `cbrt`:

```go
const (
    C = 5.42857142857142815906e-01 // 19/35 = 0x3FE15F15F15F15F1
    G = 3.57142857142857150787e-01 // 5/14  = 0x3FD6DB6DB6DB6DB7
)
s := C + r*t
t *= G + F/(s+E+D/s)
```
```csharp
const float64 C = 5.42857142857142815906e-01; // 19/35     = 0x3FE15F15F15F15F1
const float64 G = 3.57142857142857150787e-01; // 5/14      = 0x3FD6DB6DB6DB6DB7
var s = C + r * t;
t *= G + F / (s + E + D / s);
```

The guards are deliberately conservative — any doubt keeps today's `Untyped*` wrapper form:

- **Function-local only** (package-level constants keep the wrapper: they are visible across functions whose contexts may differ, including from other files of the package).
- **Untyped integer/rune/float/COMPLEX**, excluding any value on the `GoBigConst` (BigInteger) path. (Untyped complex was excluded while `complex128` had no non-`const` emission; a representable complex const now emits `static readonly` like `uintptr`, and the wrapper it replaces is actively *wrong* for it — see *A complex constant emits a complex VALUE* below. A complex use type tightens only an untyped-COMPLEX const: the converter never *widens* an untyped integer const to `complex128` because one use happens to be complex-typed.)
- **Every use must record a concrete numeric basic type** in go/types' `Info.Types`, and all uses must agree on ONE basic kind. go/types records the implicit-conversion target for an untyped operand (`float64` for `C + x` with `x float64`), so a use that stays untyped (another constant's initializer), resolves to a NAMED type or type parameter, or records a non-numeric type (`string(c)`) disqualifies — as do mixed concrete types (`float64` and `float32` uses of one constant).
- **No use may participate in constant folding**: an ancestor expression carrying a folded constant value (`uint64(B1) << 32` — cbrt's B1/B2 stay `UntypedInt`) disqualifies. Go folds untyped constant expressions at arbitrary precision; re-expressing an operand at a concrete C# type could change the folded result (or re-fold it in C#'s checked int32 arithmetic).
- The exact value is re-checked representable in the resolved type (belt-and-braces — go/types already validated each use's conversion).

A tightened constant composes with the exact-float emission above (the cbrt literals round-trip to their documented bit patterns, e.g. `C` ↔ `0x3FE15F15F15F15F1`), with the `iota` initializer (a position-0 `= iota` tightened to nint emits golib's constant bare — see the bare-iota rule below; any other tightened type keeps the folded value with the `/* iota */` comment), and with a float-KIND value under an INTEGER tightened type — `const infinity = 1e6` (go/printer; `1e6` lexes as a float literal) used only in int contexts emits the integer form `const nint infinity = 1000000;`, since a C# `1e6` double literal has no implicit conversion to nint and the tightening pass guaranteed integral representability. (Guarded by the `UntypedConstDefine` behavioral test's `tightenGuards` — single-type/append/defer/shift-operand uses tighten, mixed-type/const-feeding/folding uses keep the wrapper, and the narrow byte/int16/uint16 shifted consts keep the width retype (145, not 401), all output-compared vs Go — and by `BitwiseUntypedConst`, whose local `signBit = 1 << 63` now emits `const uint64` with the `(uint64)` operand casts dropped; `ConstShadowsParam` locks the shadow-rename interplay, its folded `int64(ns)` uses staying untightened.)

### A complex constant emits a complex VALUE, rendered from its two exact halves

A COMPLEX-kind constant is emitted as a real complex value built from its real and imaginary parts, each rendered by the same exact-float machinery a float const uses (`exactFloatText`) and recombined in the postfix `.i()` form `convBasicLit` already emits for written imaginary literals. Because `complex128` is `System.Numerics.Complex` and `complex64` is a golib struct — and C# forbids `const` of a library struct (CS0283) — the declaration is `static readonly`, the same demotion `uintptr` takes:

```go
const (
    cRational   = 5.5 + 1.5i
    cNegImag    = 2.25 - 0.75i
    cPureImag   = 3i
    cWideEnough = 1.5e308 + 1.0e307i
    cFolded     = (1 + 2i) * (3 + 4i)
)
const c64 complex64 = 1.5 + 2.5i
```
```csharp
internal static readonly UntypedComplex cRational = /* 5.5 + 1.5i */ 5.5D + 1.5D.i();
internal static readonly UntypedComplex cNegImag = /* 2.25 - 0.75i */ 2.25D + -0.75D.i();
internal static readonly UntypedComplex cPureImag = /* 3i */ 3D.i();
internal static readonly UntypedComplex cWideEnough = /* 1.5e308 + 1.0e307i */ 1.5e+308D + 1e+307D.i();
internal static readonly UntypedComplex cFolded = /* (1 + 2i) * (3 + 4i) */ -5D + 10D.i();

internal static readonly complex64 c64 = /* 1.5 + 2.5i */ 1.5F + 2.5F.i();
```

The receiver's `F`/`D` suffix selects the golib `i()` overload (`i(this float)` → `complex64`, `i(this double)` → `complex128`) exactly as a written literal's does, and each half's implicit float→complex conversion closes the `+`. A ZERO real part renders as the bare imaginary literal (`3D.i()` — the Go source form of `3i`); a NEGATIVE imaginary part composes as written, because member invocation binds tighter than unary minus, so `2.25D + -0.75D.i()` is 2.25 + −(0.75·i).

**Why the halves are tested individually.** Representability was previously decided by handing `go/constant`'s `Value.ExactString()` to `strconv.ParseComplex`. That text is the parenthesized RATIONAL form — `5.5+1.5i` is `(11/2 + 3/2i)` — which is neither C# syntax nor a form `ParseComplex` accepts (its grammar is Go *literal* syntax: no parentheses, no spaces around the sign, no `p/q`). The test could therefore never succeed: **every** complex constant, however ordinary, was classified as beyond-`complex128` and emitted through the `GoBigConst` arm — whose `BigInteger.Parse` cannot represent a complex at all. `strconv`'s `atoc_test.go` (`const want = 1.5e308 + 1.0e307i`, a value that fits `complex128` with room to spare) failed to compile on `c != want` (CS0019, `Complex` vs `GoBigConst`). Each half is now rendered and range-tested on its own at the declaration's element width; only a genuinely unrepresentable value keeps the `GoBigConst` arm, and that emission now **warns**, because it is knowingly lossy.

A FUNCTION-LOCAL untyped complex const additionally tightens to its single concrete use type (the tightening pass above), which the wrapper form cannot substitute for: `UntypedComplex` converts implicitly to *and* from `complex128`, so comparing a wrapper-typed const against a `complex128` is AMBIGUOUS (CS0034) — `atoc_test`'s `TestParseComplexIncorrectBitSize` is exactly that shape, and it emits `complex128 want = …`. (Guarded by the `ComplexConstContext` behavioral test — rational halves, a negative imaginary part, a pure imaginary, the beyond-1e308-real strconv shape, a folded complex expression, a `complex64`-typed const, and a function-local const, all output-compared vs Go.)

**A const initialized by exactly the builtin `iota` emits golib's constant bare when it can express the value.** golib's builtin declares `public const nint iota = 0` (`golib/builtin.cs`), so the initializer emits as bare `iota` — instead of the folded comment form `/* iota */ 0` — only when BOTH halves of that declaration match: the folded group-position value is `0` (position 0 of the Go const group) AND the emitted C# type accepts golib's `nint` constant, i.e. the emitted type *is* `nint` (an explicit Go `int` type, or a function-local untyped const tightened to it) or the `UntypedInt` wrapper (implicit from nint). Everything else keeps the folded form: a LATER group position folds to a value golib's constant cannot express (`x = iota` at position 1 emits `/* iota */ 1` — on the `UntypedInt` path a bare `iota` there would even compile, silently at the WRONG value), and any other emitted type — named wrappers (`ΔKind Invalid = /* iota */ 0`), other widths (`int64`) — keeps `/* iota */ N` rather than casting golib's nint. (No current emission path casts an in-range position-0 const, so no `(T)iota` form exists; should one ever require a cast anyway, the cast would wrap `iota` rather than the folded value.) The identifier must resolve to the *universe* `iota` — a user-shadowed `iota` keeps the folded value. From compress/flate's `huffmanBlock` states:

```go
const (
    stateInit = iota // Zero value must be stateInit
    stateDict
)
```
```csharp
const nint stateInit = iota; // Zero value must be stateInit
const nint stateDict = 1;
```

(Guarded by the `IotaEnum` behavioral test — position-0 bare iota at explicit `int` and via local tightening, the `int64` mismatch both explicit and tightened, and later positions on both the nint and `UntypedInt` paths; the untyped later-position `rawOne = iota` case also locks the value fix — the prior emission referenced golib's `iota` (0) for a position-1 constant (1) — and the named-wrapper enum stays folded; all output-compared vs Go.)

A Go untyped *float* constant defaults to `float64`, so its C# literal carries the double suffix `D` — not `F` — regardless of whether the value happens to fit in `float32`. (Emitting `F` whenever the value fit would make `z := 1.0` a `float`, breaking later `float64` arithmetic with CS0266.) A literal in an explicit `float32` context keeps `F`:

```go
z := 1.0           // untyped float -> float64
var f float32 = 2.5 // float32 context
```
```csharp
var z = 1.0D;
float32 f = 2.5F;
```

The `F`-vs-`D` decision needs one more step **inside a constant expression**: go/types resolves the contextual type on the *outermost* constant expression only (its `updateExprType` deliberately never descends into constant operands — they never materialize at runtime in Go), so the inner literals of `var b float32 = -3.5`, of `complex(2.5, -3.5)` in a `complex64` context, or of `-(1.5 + 2.0)` stay recorded `untyped float` and would fall back to the `D` default — emitting `-3.5D` where C# needs `-3.5F` (no implicit double→float32/complex64 conversion: CS0266/CS0019). Since the emitted C# preserves the operand structure, the converter re-propagates the resolved type down the constant shapes go/types dropped it from — parens, unary `+`/`-`, arithmetic binary operands, and the `complex`/`real`/`imag`/`min`/`max` builtin arguments, each mapping the context appropriately (a `complex64` result makes `complex(…)`'s arguments `float32`; a `float32` result makes `real(…)`'s argument `complex64`) — via `markUntypedConstContexts` (`untypedConstOperations.go`), which the literal emitter consults when the literal's own recorded type is untyped:

```go
var c64 complex64 = complex(2.5, -3.5)
var c64b complex64 = 2.5 - 3.5i
var a, b float32 = 2.5, -3.5
```
```csharp
complex64 c64 = complex(2.5F, -3.5F);
complex64 c64b = 2.5F - 3.5F.i();
float32 a = 2.5F;
float32 b = -3.5F;
```

An **imaginary literal** is emitted in POSTFIX form — Go `3.5i` becomes `3.5D.i()`, the closest C# rendering of the Go literal — via golib extension methods on the suffixed real literal. The receiver suffix drives the overload choice: `…F.i()` (`i(this float)`) returns a `complex64` and `…D.i()` (`i(this double)`) a `complex128`, so the suffix follows the literal's resolved complex type per the same propagated context — replacing the earlier fits-in-float32 heuristic, which routed `0.1i` in a `complex128` context through `complex64` and silently lost precision (`(double)(0.1f) != 0.1`). The postfix form exists because a bare `i(…)` call is poisoned by Go's single most common identifier: a local or parameter named `i` in scope binds the bare call instead of the using-static golib helper (encoding/gob `encComplex`'s `i *encInstr` parameter, `c != 0+0i` → CS0149; C# block-scope rules make even a later-declared local poison an earlier bare call, CS0135/CS0844). Member access cannot bind a local, needs zero scope analysis, and — since the F/D suffix is emitted unconditionally — the receiver always lexes as a real literal (`0D.i()`, `.25D.i()`, `1e2D.i()` all parse; a suffixless `3.i()` would not). Member invocation also binds tighter than unary minus, so `-3.5D.i()` is `-(3.5i)` exactly as in Go, down to the negative-zero real part Go prints as `(-0-3.5i)`. The prior solution was the class-qualified `builtin.i(3.5D)` — equally shadow-immune, replaced for readability; the static call form remains valid on the `this`-modified overloads. (Guarded by the `UntypedConstFloatContext` behavioral test — the shapes above plus nested parens, quotient operands, `min`/`max`, `real`/`imag` round-trips, and a named-`float32` context, values verified vs Go — by `ComplexImaginaryShadow` — the gob-shaped shadowing parameter — and by `ComplexFormat` — complex printing round-trips.)

**A golib companion to the above: `UntypedFloat`'s conversions to a complex type are EXPLICIT, not implicit.** An untyped float constant emits as a golib `UntypedFloat` (see the untyped-wrapper section), and multiplying it into complex arithmetic — Go's `1i * math.Pi`, which the imaginary rule renders `1D.i() * math.Pi` (`Complex * UntypedFloat`) — bound **ambiguously** while `UntypedFloat` converted *implicitly* to both `float64` and `complex128`: the complex operand could bind as `Complex * double` (untyped → double) OR as `UntypedFloat * UntypedFloat` (the complex → `UntypedFloat` implicit conversion), and C# prefers neither (CS0034). The fix keeps `UntypedFloat`'s float↔complex relationship explicit in **both** directions (`UntypedFloat.cs`), so such arithmetic resolves cleanly to `Complex * double` — mathematically identical, since the untyped operand is a real value. This is compile-time only (an untyped float already converts implicitly to its natural `float64`; the widening to complex is the rarer direction, where the converter emits an explicit cast). Adding `Complex`-typed operators to `UntypedFloat` instead was rejected — it *reintroduces* ambiguity (`UntypedFloat / int` then matches both `UntypedFloat op UntypedFloat` and `Complex op UntypedFloat` via `int`'s dual conversions). This unblocked `math/cmplx`'s example build. (Guarded by the same `UntypedConstFloatContext` test's `1i * gPi` / `gPi * 2i` / `1i + gPi` lines — a package-level untyped const, so the operand emits as `UntypedFloat`; the pre-fix golib fails these at compile with CS0034.)

**An untyped INT literal resolved to a floating type takes the same F/D suffix (2026-07-18).** The propagation above marks the *integer* argument `0` of `complex(0, gHalfPi)` with the `complex128` element context (`float64`), but `convBasicLit`'s int-literal arm formerly ignored that mark and emitted a bare `0` (a C# `int`). golib's `complex` builtin has both a `complex(float32, float32) → complex64` and a `complex(float64, float64) → complex128` overload, and C# rates the `int → float` argument conversion *better* than `int → double` — so `complex(0, gHalfPi)` (where `gHalfPi` is a package-level untyped float, implicitly convertible to either width) bound the **complex64** overload and recomputed `gHalfPi` at float32: `math/cmplx`'s `Atanh` of an infinite input returned `float32(π/2)` = 1.5707963705062866 where Go's `complex(0, math.Pi/2)` is float64 π/2 = 1.5707963267948966. The int-literal arm now renders a context-resolved literal at its float type (`complex(0D, gHalfPi)` → the `float64` overload), exactly as the float and imaginary arms above already do; an int has no fractional part, so its exact digits plus the `F`/`D` suffix suffice. A `complex64` context is unaffected — the float32 overload is the intended one there. The *directly*-typed float case (a `[]float64{74, …}` element) is the companion rule immediately below. One operand kind is deliberately **excluded**: an integer-kind operand of a `/` is left unmarked, because Go integer-divides untyped-int operands even in a float context (`7 / 2` is `3`, then `3.0` — not `3.5`), so pushing a float type onto it would silently switch C# to float division. A float-kind operand of the same `/` (`math.Pi / 2`, already a float division) keeps the context and its `D` suffix; the exclusion is per-operand (`isIntegerKindConstExpr` in the QUO arm of `propagateUntypedConstContext`), matching the exact-rational reasoning that already bars an *integer* context from crossing `/`. This cleared `math/cmplx`'s `TestAtanh`. (Guarded by the `ComplexConstContext` behavioral test — `complex(0, gHalfPi)`'s imaginary part as float64 π/2, plus `7 / 2 == 3` and `complex(7/2, 0)`'s integer-quotient real part, vs Go.)

**The mirror case: an IMAGINARY literal resolved to a REAL float type emits its real part, NOT `.i()` (2026-07-24).** Go permits an untyped complex constant whose imaginary part is ZERO (`0i`, value 0) to convert to a `float` parameter: `complex(math.NaN(), 0i)` — internal/fmtsort's `sort_test.go` `complex128`-key map — where `complex()`'s second parameter is `float64`. go/types records the literal's type as that `float64` (the converted type), and its REAL part (0) is what must be emitted. `convBasicLit`'s `token.IMAG` arm formerly emitted `.i()` unconditionally, producing `0D.i()` — a `System.Numerics.Complex` where the golib `complex(double, double)` overload wants a `double`, so C# reported CS1503 on BOTH arguments (`double`→`float` on arg 1, `Complex`→`float` on arg 2, as it fell back to the `complex(float, float)` candidate). The arm now checks the RESOLVED type: when it is a float (`types.IsFloat`), the real part is rendered as a plain `D`/`F`-suffixed literal (`0D`) exactly as the float and int arms do; only a complex resolved type takes the `.i()` form. Only `0i` can reach this arm — a nonzero imaginary constant is not representable as a real float, so go/types never records one with a float type — but the emission is driven off the resolved type, not that fact. This was the sole compile blocker for internal/fmtsort. (Guarded by the `resolveBuildTags`/`convBasicLit` converter unit coverage and the `ComplexConstContext` behavioral test's `complex(_, 0i)` real-context line, values vs Go.)

**A DIRECTLY float-typed int literal takes the suffix too — and an overflowing one MUST (2026-07-20).** The rule above keys off the *propagated* context (`untypedConstContexts`), which the propagation walk records only for the constant shapes it descends — parens, unary sign, arithmetic/shift operands, and the `complex`/`min`/`max`/`real`/`imag` builtin arguments. A great many int literals reach a float type by a route the walk never touches: go/types types them **directly** as a non-untyped float — a `float64`/`float32` **composite-literal element**, a typed **const**, a **function argument**, a **return** value, or an **assignment** RHS. For those, `info.Types[lit].Type` is already `float64`/`float32` (not `untyped int`), so no context mark exists and the int-literal arm formerly fell through to the ordinary integer emitter — a **bare** C# integer literal. For a small value that merely bound an int-typed overload where a float one was meant (the same latent hazard as the propagated case); for a **large** value it was a hard compile error. strconv's `ftoa_test.go` puts `123456789123456789123456789` in a `float float64` struct field; the bare 27-digit literal overflows **every** C# integral type — `error CS1021` "Integral constant is too large" — which was the sole blocker stopping the whole `strconv` test host from compiling in Phase 4. `intLiteralFloatKind` (`convBasicLit.go`) now consults **both** routes: the literal's *directly-resolved* non-untyped float/complex type first (a named type over `float64` resolves through `Underlying` to its `float64` kind, matching the FLOAT arm), then the propagated context. Either way the int-literal arm emits the same `F`/`D`-suffixed literal. The Go **source digits are preserved verbatim** whenever they also form a valid C# real literal (the visually-similar goal) — `123456789123456789123456789D` is a valid C# `double` literal that rounds to the *same* float64 the bare form overflowed on, so the emitted digits still read like the source. A radix-prefixed or legacy-leading-zero form cannot survive a pasted suffix (`0x10D` is the C# *hex integer* 269), so those re-render as the folded constant's exact **decimal** digits — the reuse of `isValidCSharpRealLiteral` (which already rejects those forms for the FLOAT arm) gates the choice. This also makes the previously-inconsistent element pair consistent: `[]float64{74, -784}` now emits `74D, -784D` (the negated sibling already rendered `-784D` via the propagated route). Value-identical throughout; the behavioral A/B footprint was 15 projects, every changed line an integer-form literal in a float/complex slot gaining a `D`/`F`. (Guarded by the `IntFormFloatConst` behavioral test — an overflowing `123456789123456789123456789` and a small `33909` in a `float64` field/var/return, a `float32` field, and a decimal-form control that stays byte-identical, output-compared vs `go run`; unfixed, the overflowing literal fails to compile with CS1021.)

**Go-only float literal forms re-render as decimal.** The suffix decisions above choose *what type* a literal is; independently, its *text* must be a form C# can parse. Both literal arms emit the Go **source text verbatim** whenever C# shares the form — the same visually-similar goal that keeps `0x4000` from flattening to `16384`, so `1.5e-3` stays `1.5e-3D` — but two Go float forms have no C# spelling and must re-render as the shortest round-trip decimal (`strconv.FormatFloat 'g'/-1`) of the go/types-folded constant:

| Go | Was emitted | Now |
|---|---|---|
| `0x1p-2` | `0x1p-2D` — CS1002, C# has no hex-float syntax | `0.25D` |
| `2.` / `1.e2` | `2.D` / `1.e2D` — C# requires digits after the point | `2D` / `100D` |
| `0x10i` | `builtin.i(0x10D)` — **silently 269** | `builtin.i(16D)` |

The imaginary row is the dangerous one: `0x10D` is a *valid* C# hex integer literal, so the pasted-on suffix changed the value with no diagnostic rather than failing to compile. An imaginary literal's mantissa is matched against `constant.Imag` of its folded (complex) value, never the whole constant.

The re-render rounds the **exact** constant straight to the literal's resolved width (`constant.Float32Val` for a `float32`/`complex64` context), never float64-then-narrow: `var j float32 = 0x1.0000010000000000001p0` is 1 + 2⁻²⁴ + a residue, so double rounding lands exactly halfway and ties-to-even *down* to `1`, while Go's single rounding sees the residue and rounds up to `1.0000001`. These forms are absent from the non-test stdlib corpus but routine in Go's own `_test.go` files (the math tests) and in user code. The shared predicate (`isValidCSharpRealLiteral`) is the same one the constant-declaration path above uses; it also rejects the Go-only *integer*-mantissa radix forms an imaginary literal can carry — octal `0o123i`, binary `0b101i`, and legacy leading-zero `0123i`/`0_123i` (octal-flavored source C# would re-read as decimal) — which re-render as their exact decimal value (`83D.i()`, `5D.i()`, `123D.i()`; guarded by the exotic-mantissa cases in `ComplexFormat`). (Guarded by the `GoOnlyFloatLiteralForms` behavioral test — hex floats, trailing-dot and `1.e2` forms in `float64`/`float32` contexts, hex-float/hex-integer/trailing-dot imaginary literals in `complex128`/`complex64` contexts, the double-rounding case above, and decimal controls proving verbatim round-trip; values verified vs Go.)

A native-sized integer constant (`nint`/`nuint`, including the `uintptr` alias) whose value does not fit a C# constant of that type — e.g. `const MaxUintptr = ^uintptr(0)` (= `0xFFFFFFFFFFFFFFFF`), a `ulong` literal that needs a *non-constant* `nuint` conversion — cannot be a C# `const` (CS0133/CS0266). It is emitted as `static readonly` with an `unchecked` cast instead (small native-int consts like `const nint iota = 0` stay `const`):

```csharp
public static readonly uintptr MaxUintptr = /* ^uintptr(0) */ unchecked((uintptr)18446744073709551615);
```

The same `unchecked` cast is emitted for a **named** constant declared over a *wide unsigned* underlying whose folded value overflows int32 — `const unknownClass = ^Class(0)` (x/text/unicode/bidi, `type Class uint`) and `const _m = ^Word(0)` (go/constant via math/big, `type Word uintptr`) both fold to the all-ones literal `18446744073709551615` (a C# `ulong`), which has no implicit conversion to the `[GoType]` wrapper struct (CS0266). The native-int-const detection, which previously fired only for a `uintptr` underlying, now also fires for `uint`/`uint64` underlyings, so the const emits `unchecked((Class)18446744073709551615)`. A **small** named const stays uncast (`const c = Class(5)` → `Class(5)`, an ordinary in-range constant conversion) — the cast is added only when the value is out of int32 range, so no other named-const emission churns. (Guarded by the `NamedNumericConstCast` behavioral test — a beyond-int32 `^Named(0)` over `uint` and over `uint64` plus a small in-range control, values verified vs Go; shared root, cleared go/constant and bidi one error each.)

**`uintptr` is a DISTINCT golib struct** (`golib/uintptr.cs`), not an alias of `System.UIntPtr`: Go's `uint` and `uintptr` are distinct types (both may appear in one type switch; `%T` reports them differently; conversion between them is explicit), and the historical alias erased that identity — type switches collided (CS8120), `%T` lied, and overloads could not distinguish them. The struct holds a single public mutable `nuint Value` field (PascalCase — it is public so `Interlocked`/`Volatile` seams can target the inner storage; the intrinsics cannot take a ref to a user struct) and carries the full operator surface so `uintptr`-typed expressions KEEP the type. The conversion matrix is empirically tuned to C#'s user-defined-conversion candidate rules (encompassing counts only STANDARD conversions, so nothing ever chains two user-defined operators; a PARTIAL outbound operator set is unstable — undeclared targets see multiple viable std-hop candidates, CS0457): implicit both ways with `nuint` plus implicit from smaller unsigned/`char`/`UntypedInt`; explicit inbound from signed types and `uint64`; the FULL exact outbound matrix (all integer widths + `float32`/`float64` + unsafe `void*`). Knock-ons handled with it: `const uintptr` is illegal C# (user struct) so every uintptr const emits `static readonly`; a uintptr-typed switch tag/label can never be a constant/relational pattern (CS9135) so those switches use the if-else `==` form; wrappers over uintptr (`[GoType("num:uintptr")]`) gain generated `nuint`/`UntypedInt` bridges; generic-math-constrained golib helpers (`unsafe.Add/Slice/String`) gain non-generic `uintptr` overloads; and the manual managed-referent types declare direct `uintptr` bridges (token out, panic-on-nonzero in).

**Numeric literal formatting is preserved** wherever Go and C# syntax overlap: hex (`0x4000`), binary (`0b1011`), and decimal literals — including `_` digit separators — emit with their original source text (`0x4000` never flattens to `16384`), keeping bit masks and addresses recognizable; required `U`/`UL`/`L` suffixes and casts compose with the preserved text (`0xFFFFFFFFU`). Go-only forms re-render as decimal: `0o…` octal has no C# syntax, and a legacy leading-zero octal (`0755`) would silently re-bind as decimal 755 in C#.

**A beyond-MaxInt64 integer literal in a `uint64` context emits a plain `UL` literal.** The emitter classifies an INT literal by parsed range, and a value above int64 (representable only unsigned — the `-Inf` bit pattern `0xFFF0000000000000`, `^uint64(0)`) previously *always* emitted `(nuint)0x…UL`. That prefix is the bridge needed when the literal's resolved type is Go `uint`/`uintptr` (C# `nuint` — a bare `ulong` literal has no implicit conversion to it, CS0266, while the non-constant unchecked `(nuint)` conversion compiles), but in a `uint64` context it is spurious: semantically wrong for a 64-bit target type and value-truncating on a 32-bit platform — `math.Float64frombits(0xFFF0000000000000)` emitted `Δmath.Float64frombits((nuint)0xFFF0000000000000UL)` while the int64-range `0x7FF0000000000000` emitted clean (the signed branch already consulted the resolved type). The emitter now checks the literal's resolved *underlying* type: `uint64` — including a named type over `uint64`, whose `[GoType]` wrapper converts implicitly from `ulong` — takes the plain `0xFFF0000000000000UL`; native-width unsigned targets keep the `(nuint)` cast. This also cleans the same pattern from stdlib constant tables on the next regen (crypto/sha512's K, crypto/des masks, nistec field elements). (Guarded by the `MathFloatBits` behavioral test — ±Inf bit patterns as `uint64` arguments plus var-decl, comparison-operand, and binary-mask contexts, values verified vs Go; the `BitwiseUntypedConst`/`NamedIntSignednessConv`/`ShiftPrecedenceUnsigned` goldens re-baselined to the cast-free form, and `LargeUintptrConst` pins the native-width path unchanged.)

**A constant expression whose SUBexpression overflows the target type narrows once at the whole expression.** Go evaluates constant arithmetic in **arbitrary precision** and requires only the FINAL value to be representable in the target type — a subexpression is free to overflow it, so `[]int32{1<<31 - 1}` is legal Go even though the inner shift is 2147483648. C# has no such rule: it would compute the operators in `int` and overflow at compile time (CS0220), which is why an out-of-int32-range constant subexpression FOLDS to a C# `long` literal (`2147483648L`) in the first place. That fold widens the WHOLE element rendering to `long`, and `long` converts implicitly to none of the narrower integer targets — so the emission must narrow back exactly once:

| Go | Was emitted | Now |
|---|---|---|
| `[]int32{1<<31 - 2}` | `2147483648L - 2` — CS0266 | `(int32)(2147483648L - 2)` |
| `[]uint32{1<<32 - 1}` | `4294967296L - 1` — CS0266 | `(uint32)(4294967296L - 1)` |
| `[]uintptr{1<<40 + 1}` | `1099511627776L + 1` — CS0266 | `(uintptr)(1099511627776L + 1)` |
| `bits & (1<<52 - 1)` (uint64) | `bits & (4503599627370496L - 1)` — CS0019 | `bits & ((uint64)(4503599627370496L - 1))` |

The narrowing applies to every integer target EXCEPT `int64`, whose C# `long` already *is* the widened width, and it fires at the emission itself — in the parenthesized `(type)(…)` form `wholeExprIsCastOfType` recognizes — so it reaches composite elements, arguments, and comparison operands, not only the assignment position the sibling `nativeIntConstCastType` covers (which keeps handling the out-of-int32-range values folded whole; the cast strings match, so neither re-wraps the other). Two scope restrictions carry over from that sibling: at least one OPERAND must itself fold to a `long` literal — a bare `1 << 40` (both operands small) emits as a 32-bit `1 << (int)(40)` whose count C# MASKS to 8, and casting *that* would convert a loud error into a silently wrong value — and the whole value must be int64-exact, so a `uintptr` past int64 range keeps its visible error rather than being masked. Requiring a folded operand also guarantees the *non*-folded operands compute exactly, since a shift is only left unfolded when its value fits int32, which bounds its count below 32. A subexpression that stays inside int32 therefore keeps its readable operator form (`1<<20 + 1` → `(1 << (int)(20)) + 1`), and a whole value already past int32 still folds outright (`1<<63 - 1` → `9223372036854775807L`).

Corpus effect: this repaired latent `ulong`-versus-`long` mismatches across crypto/aes, crypto/cipher, database/sql/driver, math/big, net/http, runtime, strconv, sync, and vendored chacha20poly1305, and made math/rand's `Int31n` compute in `uint32` exactly as Go does (it previously computed the same value in `long`). The `1<<31 - 1` / `1<<63 - 1` idiom is pervasive in Go's own `_test.go` files, where the shape is a hard compile blocker. (Guarded by the `ConstSubexprOverflow` behavioral test — int32/int16/uint32/uint64/uintptr/int elements, the int64 no-cast case, in-range controls, and assignment/explicit-conversion/argument positions, values verified vs Go.)

**The narrowing root can be a UNARY node, and the widening fold can be arbitrarily deep (2026-07-25).** Two shapes escaped the rule above because it only ever looked at a `*ast.BinaryExpr` and only at that node's two *direct* operands.

1. **A negated widened constant roots at a unary node.** go/types types only the ROOT of a constant operator expression and leaves its operands untyped (`updateExprType` stops descending once the node it is retyping is itself constant), so `[]int32{-(1<<31 - 1)}` records `untyped int` on the inner `1<<31 - 1` and `int32` only on the negation. There is no typed *binary* anywhere in the tree to hang the cast on, so nothing narrowed and the element emitted a bare `-(2147483648L - 1)`. strconv's `atoi_test` parseInt32 table — `{"-2147483647", -(1<<31 - 1), nil}` against an `int32` struct field — is exactly this (CS1503). `widenedConstExprCastType` now accepts a unary root too and `convUnaryExpr` applies the cast at its own emission, mirroring `convBinaryExpr`: `(int32)(-(2147483648L - 1))`. `^` takes the same treatment (`(int32)(~(2147483648L - 1))`) and an `int` target narrows to `nint`; the non-constant unary operators (`&x`, `<-ch`, `!b`) are excluded by the existing constant-value and integer-kind guards.
2. **The fold need not be a DIRECT operand.** The operator form is emitted over the operand *renderings*, so a subtree that does not fold itself still renders `long` when one of *its* operands does: in `[]int32{(1<<31 - 1) - 1}` the root's operands are `(1<<31 - 1)` (value in range, unfolded) and `1`, and only the grandchild shift folds — likewise `[]int32{1<<40>>20 - 1}`. `operandRendersWidenedFold` now descends the whole constant subtree instead of testing one level. Descent cannot over-report: because go/types leaves the operands of a constant operator expression untyped, no interior node ever carries a narrowing cast of its own, so exactly one cast is emitted at the root — the guard test pins `(int32)((2147483648L - 1) - 1)`, not a doubled form.

The unary root is also taught to the explicit-conversion path (`int32(-(1<<31 - 1))`), which returns the operand's own cast rather than doubling it — the same `wholeExprIsCastOfType` check that path already applied to a binary operand. (Guarded by the `ConstSubexprOverflow` extension — negated/`^`/deep-fold elements for `int32`, `int`, and an in-range `int16` control, plus a typed assignment, an explicit conversion, a struct-field table entry and two call arguments, values vs Go; counter-proven against the pre-fix converter, whose emission fails to compile with ten CS0266/CS1503 on exactly those positions.)

**A subexpression past INT64 under a NATIVE-WIDTH unsigned target folds with its own `(nuint)` cast (2026-07-20).** The narrowing above needs the whole value to be int64-exact, and the *fold* that produces its widened operand originally ran only under a plain `uint64` target — a native-width target (`uint`/`uintptr`, and any named type over them) was left with its visible error, since `nuint` has no implicit conversion from `ulong` and the fold could not name the target. Go's arbitrary-precision rule makes this shape ordinary in numeric code: math/big's `nat{0, 0, 1 + 1<<(_W-1), _M ^ (1 << (_W - 1))}` (`int_test.go`'s `TestQuoStepD6`, where `Word` is a named type over `uintptr` and `_W` is 64) has an inner `1 << 63` of 9223372036854775808 — past int64 entirely, so no signed `long` fold can carry it — while each element's own value is representable in `Word`. Left alone, C# computed the element in int32: `1 + (1 << (int)(63))` against a `Word` element (CS0029), and `(nuint)_M ^ (1 << (int)(63))` mixing `nuint` with `int` (CS0019).

The fold now covers `uint64` **and** both native-width spellings — Go `uint` renders as `nuint`, Go `uintptr` as golib's distinct `uintptr` struct — and carries the narrowing itself for the native-width pair, in the same parenthesized form `wholeExprIsCastOfType` recognizes as the `(nint)(…)` arm on the signed side:

| Go | Was emitted | Now |
|---|---|---|
| `[]Word{1 + 1<<63}` (`Word uintptr`) | `1 + (1 << (int)(63))` — CS0029 | `(nuint)(9223372036854775809UL)` |
| `[]uintptr{_M ^ (1 << 63)}` | `(uintptr)_M ^ ((1 << (int)(63)))` — CS0019 | `(nuint)(9223372036854775807UL)` |
| `[]uint{1 + 1<<63}` | `1 + (1 << (int)(63))` — CS0029 | `(nuint)(9223372036854775809UL)` |
| `[]uint64{1 + 1<<63}` | `9223372036854775809UL` | *unchanged* |

The cast is spelled `nuint` for both native-width targets rather than naming the target: it is the primitive C# native unsigned type, and it converts implicitly to golib's `uintptr` struct and to a `[GoType]` wrapper over `uintptr` alike — so one spelling covers `uint`, `uintptr`, and named types over either, with no target-name synthesis. The `uint64` emission is untouched (it already had an implicit conversion from `ulong`), so this is zero-churn on the existing corpus — CNR is byte-identical across all 434 behavioral projects. (Guarded by the same `ConstSubexprOverflow` behavioral test, extended with a named-`uintptr` `Word` type plus plain `uintptr`/`uint`/`uint64` elements of the beyond-int64 shape, values verified vs Go.)

See [Named Numeric Types and Constant Contexts](#named-numeric-types-and-constant-contexts) for how these interact with native-int and named numeric types. See also [example](https://github.com/ritchiecarroll/go2cs/tree/master/src/Examples/Manual%20Tour%20of%20Go%20Conversions/basics/numeric-constants).

## Native and Narrow Integer Types

In Go the `int` and `uint` types are sized according to the platform build target, i.e., 32-bit or 64-bit. C#'s `int`/`uint` are always 32-bit and `long`/`ulong` are always 64-bit. As of C# 9.0, native-sized integer types exist that behave exactly like their Go counterparts: [`nint` and `nuint`](https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#performance-and-interop). The converter maps Go `int` → `nint` and Go `uint` → `nuint`; `uintptr` also maps to `nuint`. The fixed-width Go types (`int8/16/32/64`, `uint8/16/32/64`, `byte`, `rune`) are kept as readable C# aliases of the same name (e.g. `global using uint16 = System.UInt16;`).

**Narrow-integer arithmetic.** A subtle semantic gap: Go evaluates arithmetic on a sub-`int`-width integer (`int8`/`uint8`/`int16`/`uint16`) at that operand's own width, with overflow **wrapping** — `var a, b uint8 = 200, 100; a + b` is `44` (300 mod 256). C#, however, **promotes** arithmetic on `byte`/`sbyte`/`short`/`ushort` to `int`, so `a + b` is `300` and is *not* implicitly assignable back to the narrow type. Where a narrow-arithmetic result is used in a context that requires the narrow type — e.g. passed to a narrow-typed parameter — the converter emits an explicit cast back to that type, which both compiles (the implicit `int`→narrow conversion is rejected, CS1503) and restores Go's wrapping:

```csharp
takeU8((uint8)(a + b));   // Go take(a + b), a/b uint8 → 44 (wraps), not 300
takeU8((uint8)(~a));      // Go take(^a) → 55
```

The same cast applies in the **assignment** context — a narrow-arithmetic value assigned to a narrow variable, array/slice element, or struct field (`y := a + b; y = y + 1; arr[0] = a + b; bx.b = a + b`) — and in the **declaration** context — a typed-var initializer (`var z uint8 = a + b`). All emit `(uint8)(a + b)` for the same two reasons. (A double cast is avoided when another path already narrowed the RHS, e.g. a bitwise op with an untyped constant emits its own `(byte)(b | 128)`.)

The cast is applied only when the value's Go type already matches the target (parameter / LHS / declared type), so Go accepts it without a conversion, and only for an arithmetic (binary/unary) expression — a bare identifier is already the narrow type. (Guarded by the `NarrowArithmeticArg` behavioral test, which verifies the wrapped values match Go across all four contexts. Wider integer types — `int32`/`uint32` and up — are not promoted by C# and need no cast.)

A redundant-cast guard on this decision — skip the cast when the converted RHS is *already* a full narrowing (`(byte)(b | 128)`) — must distinguish a WHOLE-expression cast from one that only converts the FIRST operand. `buf[i] = byte(e/100) + '0'` (runtime `print.go`) emits the RHS `(byte)(e / 100) + (rune)'0'`, which *starts* with `(byte)(` but only casts `e/100`; the binary result is still `int` (the `(rune)'0'` promotes it), so the narrowing cast is still required (CS0266). The guard therefore checks that the cast-paren's matching close is at the very end of the RHS (a parenthesis-balance walk that skips `(`/`)` inside char/string literals), not merely that the RHS begins with `(byte)(`. (Guarded by the `NarrowByteArithFirstOperandCast` behavioral test — including a wrapping case; cleared 3 runtime CS0266 in `print.go`'s exponent formatting.)

The **per-argument** cast path (`convExprList`, which applies `castArgToType` — e.g. an `append` element cast to the slice's element type) carries the same redundant-cast guard and shares the same whole-expression test. It previously used a bare `strings.HasPrefix` check, which the narrow-shift result cast above newly exposed: `append(s, uint16(v[0])<<8 + uint16(v[1]))` (`vendor/golang.org/x/text`'s BMP-string decoding, `crypto/x509`) emits `(uint16)(…<<8) + (uint16)v[1]`, whose leading `(uint16)(` covers only the shifted first operand while the sum still promotes to `int` — so the prefix test wrongly skipped the element cast that the whole expression still needs. Both sites now call the same balance-walk helper, so a first-operand-only cast is never mistaken for a whole-expression narrowing.

The same narrowing applies to a **`return` of narrow-integer arithmetic**. `func lowerASCII(c byte) byte { return c + ('a'-'A') }` (runtime `env_posix`) returns `byte + int` (the untyped char constant promotes to `int`) → CS0266 against the `byte` result type. The cast was applied on the assignment and value-spec paths but not the return path; it is now applied in `visitReturnStmt` when the function's result type at that position is narrow and the returned expression is binary/unary arithmetic (reusing the same gate — a bare identifier, a call, or an already-whole-expr-narrowed return is untouched, and a non-narrow result type is unaffected). (Guarded by the `NarrowByteArithReturn` behavioral test — a per-branch return plus a wrapping case; cleared the `env_posix.lowerASCII` CS0266.)

The same narrowing applies to a narrow-arithmetic **comparison operand** (2026-07-18). A narrow (`int8`/`uint8`/`int16`/`uint16`) NON-constant arithmetic/complement result compared *directly* — with no narrow destination to force the cast — keeps its C# `int`-promoted value and so compares WRONG: `int8(MaxInt8) + 1 != MinInt8` evaluates `128 != -128` (true) where Go wraps at int8 width to `-128 != -128` (false). `convBinaryExprCore` now wraps each comparison operand (`==` `!=` `<` `<=` `>` `>=`) that is a narrow non-constant arithmetic expression at its own width — `(int8)(v + 1) != MinInt8` — through the same `narrowArithmeticCastTypeFor` helper (`narrowComparisonOperand`), gated to NON-constant operands: a Go constant cannot overflow its type, and a wrap cast on a C# compile-time constant expression is CS0221 (the shift-retype path guards identically). Unlike the destination contexts, this had no compile symptom — the code compiled and only the *value* of the comparison was wrong — so it surfaced only when `math`'s `TestMaxInt`/`TestMaxUint` ran. (Guarded by the `NarrowArithmeticArg` behavioral test's comparison cases `a+b == 44` / `c+d == -56` / `e+f != 4464` / `^a > 50`, output-compared vs Go.)

A related **wide** case: a computed *constant* arithmetic expression assigned to a **native-width integer** (`uintptr`/`uint`/`int` → C# `nuint`/`nint`) whose folded value overflows int32. `pattern = 1<<maxBits - 1` (runtime `mbitmap`, `maxBits` = 57) is a `uintptr` constant, but the converter folds the untyped sub-shift `1<<maxBits` to a **signed** C# `long` literal (`144115188075855872L`, since it exceeds int32 and the untyped operand is treated as signed), so the whole RHS is `long` — which has no implicit conversion to the native target (CS0266). A `UL`/`(nuint)` suffix would not help (`ulong`→`nuint` is also an explicit conversion). The converter wraps the whole RHS in the native target's cast: `pattern = (uintptr)(144115188075855872L - 1)`. This fires **only** when the constant fits int64 but is out of int32 range — exactly the signed-`long` fold range. A value that overflows *int64* (a large unsigned `uintptr` like `1<<63 + 1<<62`) is deliberately left alone: its sub-shift already mis-emits (a `1<<63` int-shift), so casting it would convert a visible compile error into a silent wrong value — that is a separate defect to fix on its own, not to mask. (Guarded by the `NativeIntWideConstAssign` behavioral test — `uintptr`/`uint`/`int` targets with int64-range constants, values verified vs Go; cleared the `mbitmap` CS0266, the last one in `runtime`.)

**A runtime shift count that can reach the operand width uses Go-semantics helpers (2026-07-18).** Go zeroes a shift whose count reaches or exceeds the operand's bit-width — `x >> n` / `x << n` with `n >= width` is `0` (a SIGNED right shift sign-extends: `0` for a non-negative value, `-1` for a negative one). C#'s native `>>`/`<<` instead **mask** the count (`n & 63` for a 64-bit operand, `n & 31` for 32-bit, and sub-`int` operands promote to `int` and mask by 31), so a native shift by a runtime count silently yields the wrong value once the count can reach the width — `math.FMA`'s double-word funnel shifts (`u2 >> (64 - n)` at `n == 0`: Go `>>64`=0, C# `>>0`=`u2`) and `math.RoundToEven`'s `>> e` (`e` up to 1024 for a NaN) both corrupted. The converter keeps the native shift ONLY when the count is PROVABLY in `[0, width)`: **R1** a constant in range (the majority of shifts — every `x >> 5`; a constant `>= width` routes to the guard, which returns 0); **R2** a syntactic mask `y & M` with constant `M <= width-1`; **R3** a modulo `y % M` with constant `M <= width`. Everything else — a bare variable count (`x >> s`, which genuinely can exceed width, as RoundToEven's `e` proves, so it cannot be trusted) or an arithmetic count (`64 - n`, `shift - e`) — routes through golib's `GoShift.Rsh`/`Lsh` extension methods, `x.Rsh(n)` / `x.Lsh(n)` (the naming echoing Go's `math/big.Int.Rsh`/`Lsh`): one guarded shift per operand width returning 0 (or sign-extending) at `n >= width`. The count is taken as a wide `uint64` so a computed count like `64 - n` that unsigned-wraps to a huge value is compared at FULL magnitude BEFORE narrowing to the `int` shift amount — truncating to `int` first would defeat the guard. A named-`[GoType]`-wrapper left operand keeps its generated-operator path, as does a compound untyped-const `UntypedInt` left operand (its own `operator<<`, the `UntypedIntWideShift` subject) — but the WRAPPER OPERATOR now carries the guard itself (2026-08-09), because leaving it native left that entire family with the masked answer and no other layer covers it. `NumericTypeTemplate`'s `operator <<` / `operator >>` emit `value.m_value.Lsh((uint64)shift)` / `.Rsh(…)` in place of the native `value.m_value << shift`, so a named integer type shifts with Go's semantics wherever the converter's own provability analysis cannot reach. What motivated it was not a wrong number but a **hang**: `math/big`'s `lehmerSimulate` reads `a2 = B.abs[n-2] >> (_W - h)` on `Word` (`type Word uint`), and for a normalized operand `h == 0`, so the count is exactly 64 — Go yields 0, C# yielded the word itself. The corrupted Lehmer cosequences make `GCD`'s `for len(B.abs) > 1` loop stop converging: an INFINITE LOOP inside `math/big`, reached from `crypto/elliptic`'s generic `CurveParams` path, so `elliptic.P256().Params().Double(Gx, Gy)` never returned. That is `crypto/ecdsa`'s `TestINDCCA/P256/Generic`, carried on the board as a 20-minute timeout open between "performance gap or hang"; the fixed path runs in 0.31 s against Go's 0.66 s, so it was never slowness. It is value-dependent, which is why it hid so long — a garbage `a1`/`a2` that fails Collins' stopping condition immediately costs only a Euclidean step, so equal-width operand pairs pass and only pairs that make the condition iterate corrupt anything. `operator >>>` stays native: Go has no unsigned-right-shift operator, so nothing converted ever calls it. Measured footprint: of ~3,556 corpus shifts, ~80% (constant, named-const, and masked/modulo counts) stay native and byte-identical; only the ~20% of unprovable variable/arithmetic counts become `.Rsh`/`.Lsh` — the SOUND floor without value-range analysis (a lightweight loop-bound/range recognition could shrink it later). This replaces the narrow-shift result retype for a runtime count (`(byte)(cb << (int)(k))` → `cb.Lsh(k)`, the `Lsh(byte)` overload doing both the width wrap and the `k>=8`→0). (Guarded by the `GoShiftSemantics` behavioral test — 64/32-bit unsigned shifts by runtime counts 0/1/63/64/65/200, signed sign-extension, and R2/R3 masked/modulo counts staying native, output-compared vs Go; cleared `math`'s `TestFMA` + `TestRoundToEven`, whose funnel shifts now emit guarded automatically. Extended 2026-08-09 with the NAMED forms — `type word uint` / `halfword uint32` / `signedword int64` shifted by the same runtime counts, including the `w >> (W - h)` shape `lehmerSimulate` writes, which is the wrapper-operator route rather than the guard route.)

**The signed integer minima sign-fold at the unary level.** Go folds `-literal` into one constant, but the emitter classifies the POSITIVE operand literal alone, and both signed minima's magnitudes overflow their own type: `[]int32{-2147483648}` (internal/fuzz mutator's `interesting32`) saw 2147483648 > MaxInt32 and emitted `-(nint)2147483648L`, which has no implicit conversion back to an int32 slot (CS0266); the int64 minimum's operand 9223372036854775808 does not even parse as int64, routing through the unsigned branch to `-(nuint)9223372036854775808UL` — and C# defines no unary minus on `nuint` at all (CS0023). `convUnaryExpr`'s `token.SUB` handling now mirrors its FLOAT arm: for an INT literal operand it classifies the range on the **unary expression's resolved (sign-folded) constant**. The exact int32 minimum in an int32-typed context emits the plain negated literal `-2147483648` — C# special-cases the negated decimal int-min as an `int` constant, **by value**, so `_` digit separators survive (`-2_147_483_648` compiles, proven by the guard) — and the exact int64 minimum emits `-9223372036854775808L` (the matching `long` special case), wrapped as `((nint)(-9223372036854775808L))` in a Go-`int` context where `long` has no implicit conversion. Decimal source formatting is preserved per the literal-formatting rule; hex/binary re-render as decimal (C# has no signed special case for those forms — `-0x80000000` binds as a `long`-typed expression). Everything else keeps the default path: in-int32 operands never had a problem, and a folded int32-min in a WIDER context (`var x int64 = -2147483648`, or boxed to `any` where Go-`int` must stay `nint`) keeps the implicitly-convertible `-(nint)…L` form — the full-stdlib A/B footprint was exactly the one mutator.cs line. (Guarded by the `IntMinLiterals` behavioral test — int32-min plain and underscored in `[]int32`, int64-min in `[]int64`, the nint-min `:=` form, between-minima and non-minimal negative controls, and min-value comparisons, values vs Go; the pre-fix converter fails it CS0266 ×2 + CS0023 ×2.)

**Constant-literal return inside a lambda with an unsigned result (delegate-type inference, CS8917).** A Go closure assigned to a local — `casePC := func(casi int) uintptr { if pcs == nil { return 0 }; return pcs[casi] }` (runtime `select.go`) — is emitted as `var casePC = (nint casi) => { … };`, whose delegate type C# must **infer from the return-expression types**. The literal `return 0` is typed `int`; `return pcs[casi]` is typed `nuint` (`uintptr`). C#'s best-common-type algorithm uses the expression types (not constant convertibility), and `int` has no common type with `nuint`/`uint`/`ulong` (there is no implicit `int`→unsigned conversion for a non-constant), so the `var` assignment fails with CS8917 ("no best type found for the lambda"). The converter casts the literal to the result type so both returns share it: `return (uintptr)(0)`. Gated tightly to avoid churn and new errors: only **inside a lambda body** (`conversionInLambda` — a *named* func's `return 0` to a `nuint` result compiles as an ordinary constant conversion and needs no cast), only for a bare **integer literal** (the sole shape that trips the `int`-vs-unsigned inference gap — `byte`/`uint16` widen to `int`, and the signed/`nint`/`long` kinds share a common type with `int`, so those never hit CS8917), and only when the result is a **basic** `uint`/`uint32`/`uint64`/`uintptr` (a *named* type over an unsigned kind is left alone — `(gclinkptr)(0)` would only compile if that type defined an int conversion, so casting it could introduce a new error). Runs after the narrow-arithmetic return cast, with which it is disjoint (that handles binary/unary arithmetic on sub-`int` types; this handles a bare literal to a wide unsigned type). (Guarded by the `ClosureMixedReturnUnsigned` behavioral test — `uintptr`/`uint64`/`uint32`/`uint` mixed-return closures plus a signed control that stays uncast, values verified vs Go; cleared the `select.go` `casePC` CS8917.)

**A beyond-int32 integer constant takes the width of the type it RESOLVED to, not the untyped default (2026-08-08).** A Go integer constant outside the C# `int32` range cannot be written bare in a native-width slot — `long` has no implicit conversion to `nint` — so the converter wrapped every one of them in `(nint)…L`. That is right only when the constant really is a Go `int`. When it resolved to `int64` the cast is the wrong type: `int64` **is** C# `long`, so the digits alone denote it exactly, and the `(nint)` both truncates on a 32-bit target and reads nothing like the Go source. The compiler says so — `CS8778`, "constant value may overflow `nint` at runtime" — and 607 of the corpus's 620 such warnings were one table, `math/rand`'s `rngCooked [607]int64`:

```csharp
// before — every element carries the untyped-int DEFAULT type
internal static array<int64> rngCooked = new int64[]{
    -(nint)4181792142133755926L, -(nint)4576982950128230565L, (nint)1395769623340756751L, …

// after — the element type the Go source declares, and the Go source's own digits
internal static array<int64> rngCooked = new int64[]{
    -4181792142133755926L, -4576982950128230565L, 1395769623340756751L, …
```

The cause is a deliberate go/types behavior that stays invisible until you look for it: `updateExprType0` short-circuits with *"if x is a constant, the operands were constants"* and does **not** descend into the operands of a constant expression, because in Go they never materialize at runtime. So in `[...]int64{-4181792142133755926, 1395769623340756751}` the NEGATED element records `untyped int` while its positive sibling records `int64` — purely because one is wrapped in a unary minus. Every element of `rngCooked` is negative, which is why that whole table lost its element type while positive-only tables elsewhere kept theirs. `convBasicLit` therefore resolves the literal's integer type from **two** routes, exactly as it already does for the float `F`/`D` suffix: the type go/types recorded directly, and failing that the contextual type `markUntypedConstContexts` propagated (which already pushes an integer context through unary `+`/`-`/`^` and arithmetic operands). An `int64` resolution emits the bare `…L`; everything else keeps `nint` — it must, because an `any` slot has to box a Go `int` as `nint` so a later `x.(int)` succeeds — but as `unchecked((nint)…L)`, which is what makes a beyond-int32 **constant** conversion legal without the warning (`nint` is 64-bit on every platform go2cs targets, so the value is exact). The same `unchecked` covers the other two emitters of a native-width constant: `convBinaryExpr`'s constant FOLD (`unchecked((nint)(4611686018427387903L))` — `bufio`'s `maxInt/2`) and `csNintLiteral`'s array LENGTH (`unchecked((nint)140737488355327)` — runtime's `(*[maxAlloc/2 - 1]byte)` casts). The fold keeps its parenthesized `(T)(…)` body: `wholeExprIsCastOfType`, the redundancy guard 17 call sites share, now peels an `unchecked(` wrapper first, so enclosing paths still recognize the cast and do not re-wrap it into `(nint)(unchecked((nint)(…)))`. (Guarded by `nativeIntConstWidth_test.go` — the negated `int64` element and its positive sibling, the `int` var and the `any` slot both taking `unchecked`, the fold NOT double-wrapped, an in-range constant untouched, plus a unit test pinning the recognizer's peel. Corpus effect: `CS8778` 620 → **0**.)

> One sticking point: not all C# indexing constructs accept a `nint`. Explicit indexers support `nint`, but [implicit index support](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/ranges#implicit-index-support) (the `Index`/`Range` syntax) currently only works with `int`, so range-operation indices are cast to `int` where needed. (The earlier strategy of compiling to `long`/`ulong`, or of custom `@int`/`@uint` structs selected by a `TARGET32BIT` directive, has been superseded by `nint`/`nuint`.)

## Named Numeric Types and Constant Contexts

General untyped constant representation is covered in [Constant Values](#constant-values). This section
records the places where constants and operators become difficult because a numeric context is already
known: named numeric wrappers, native-width targets, typed-element contexts such as `append`, shift and
bit-mask operands, and the casts needed to keep C# overload resolution aligned with Go.

This area is where Go's flexible numeric model meets C#'s stricter one, and it has a few moving parts worth calling out.

**Untyped constants.** As noted under [Constant Values](#constant-values), an untyped Go constant becomes a golib `UntypedInt`/`UntypedFloat`/`UntypedComplex`. These wrappers define implicit conversions to **and from** every numeric type so the value can slot into whatever context uses it, just like an untyped Go constant. The trade-off is that mixing an `UntypedInt` directly into heavily-typed arithmetic (e.g. `someUint64 * untypedConst`) can become ambiguous to C#'s overload resolution, since the wrapper is convertible in either direction. A *function-local* untyped constant whose every use resolves to one concrete basic type sidesteps the wrapper entirely — it is declared AT that type with the per-use casts dropped (see [Constant Values](#constant-values), *function-local untyped constants tighten*); the wrapper-cast machinery below applies to the remaining wrapper-emitted constants (package-level, mixed-context, and folding-participating locals).

One resolved instance: an argument to the **`min`/`max` builtins** that is a named untyped constant renders as its `UntypedInt` static, which golib's `min<T>(T, params ReadOnlySpan<T>)` overloads reject (CS1503 — params-span element binding does not apply the user-defined implicit conversion): runtime `min(n, maxObletBytes)` (`mgcmark.go`, `uintptr` sibling) and `min(debug.profstackdepth, maxProfStackDepth)` (`runtime1.go`, `int32`). The converter casts such an argument to the call's Go-resolved result type — `min(n, (uintptr)(maxObletBytes))` — and, once one argument is cast, every constant-valued sibling too (`min(big, limit, 500)` → `…, (uintptr)(limit), (uintptr)(500))` — a bare literal is a C# `int` and would break `T` inference against the cast type). Typed arguments and literal-only calls are unchanged. (Guarded by the `MinMaxBuiltin` extension — untyped consts typed by `uintptr`/`int32` siblings plus the mixed literal case, values vs Go.)

**Named numeric types.** A Go type definition over a numeric base — `type Celsius float64`, `type level int`, `type Flags uint` — is emitted as a partial struct carrying a `num:` `[GoType]` attribute, and the `TypeGenerator` source generator fills in the body:

```csharp
[GoType("num:nint")]  partial struct level;   // type level int
[GoType("num:nuint")] partial struct Flags;   // type Flags uint
[GoType("num:float64")] partial struct Celsius; // type Celsius float64
```

The generated struct wraps the underlying value and implements the comparison and arithmetic operators plus implicit conversions to/from the underlying type, so the named type is a distinct C# type that still behaves like its base.

**Increment / decrement.** Go allows `c++` / `c--` on a named integer (e.g. a `for c := chunkIdx(0); …; c++` loop counter). The generator therefore emits `operator ++`/`operator --` returning the **named type** — `operator ++(T value) => (T)(value.m_value + (U)1)` (U the underlying). Without a dedicated operator, C# `c++` falls back to the implicit conversion to the underlying and re-assigns the (promoted) result, which for a **native-int**-backed named type (`num:nuint`/`num:nint`) promotes to `ulong`/`long` and then cannot implicitly convert back to the named type (CS0266). The dedicated operators keep the result in the named type. (Guarded by the `NamedNumericIncDec` behavioral test — `++`/`--` on `uint`- and `int`-backed named types in loop counters; runtime uses this for `chunkIdx`/`arenaIdx`/`statDep` loop counters, ~7 CS0266.)

**Unsigned underlying types and unary minus.** Go permits unary minus on an unsigned value (it wraps: `-x == 0 - x`). C# does **not** allow the unary `-` operator on unsigned operands. So the generator's `IsUnsignedType` check *omits* the unary negation operator for unsigned underlying types (`uint8/16/32/64`, `byte`, `uintptr`, and the native `nuint`/`uint`). Go's unary minus on such a value is instead lowered by the converter to the equivalent subtraction-from-zero form:

```go
var b Flags = 2
_ = -b            // Go: unsigned unary minus (wraps)
```
```csharp
Flags b = 2;
_ = ((Flags)0 - b);   // C#: lowered to (T)0 - x
```

This keeps the generated numeric struct compilable (a `(T)(-value.m_value)` body over `nuint` is a CS0023 error) while preserving Go's wrap-around semantics. The same `(T)0 - x` lowering is used for unsigned unary minus on built-in unsigned values.

**Converting *to* a named numeric type.** The generated struct's implicit conversions are only between the named type and its *exact* underlying basic (`traceArg ↔ uint64`, `arenaIdx ↔ nuint`). So a Go conversion `traceArg(procs)` where `procs` is `int32`, or `arenaIdx(1 << b)` where the shift is `int`, has no matching operator — a plain `(traceArg)procs` is CS0030. The converter coerces the argument through the underlying type first, which is exactly Go's numeric-conversion semantics:

```go
var procs int32 = 5
a := traceArg(procs)   // type traceArg uint64
b := arenaIdx(1 << 4)  // type arenaIdx uint
```
```csharp
int32 procs = 5;
var a = ((traceArg)(uint64)procs);   // through the underlying uint64
var b = ((arenaIdx)(nuint)(1 << 4)); // through the underlying nuint
```

When the argument is *already* the underlying basic (`traceArg(u)` with `u uint64`), the existing single cast already binds, so no extra cast is inserted (no churn). (Guarded by the `NamedNumericConversion` behavioral test; runtime exercises this pervasively for `traceArg`, `arenaIdx`, `traceTime`, the `abi` offset types, etc.)

**Converting *from* a named numeric type.** The mirror direction has the same root: because the wrapper only converts between the named type and its *exact* underlying, a Go conversion *from* a named numeric *to a different basic numeric* — `uint64(nameOff)` where `type NameOff int32`, or `int(idx)` where `type idx uint` — has no matching operator (`(ulong)NameOff` / `(nint)idx` is CS0030). The converter routes it through the named type's underlying basic first — the named→underlying `[GoType]` operator followed by an ordinary numeric C# cast:

```go
var s NameOff = 7  // type NameOff int32
e := uint64(s)     // NameOff -> uint64
var i idx = 9      // type idx uint
f := int(i)        // idx -> int
```
```csharp
NameOff s = 7;
var e = ((uint64)(int32)s);  // through the underlying int32
idx i = 9;
nint f = ((nint)(nuint)i);   // through the underlying nuint
```

When the target basic *is* the named type's exact underlying (`int32(s)` for `NameOff`), the single operator already binds, so no extra cast is inserted (no churn). (Same `NamedNumericConversion` behavioral test; runtime hits this on the `abi` offset types `NameOff`/`TypeOff`/`TextOff` → `uint64`/`uintptr`, `taggedPointer`/`traceTime` → `int64`, etc.)

**Cast parenthesization (visual fidelity).** A Go conversion `T(x)` reads as a function call; the C# cast `(T)x` is the closest equivalent, so the converter keeps it minimally parenthesized to stay close to the source:

* **Basic-target conversions omit the outer wrap.** A conversion whose target is a *basic* C# type — `uint64(a)`, `int(k)`, `float64(b)`, and even `unsafe.Pointer(p)` (go/types models it as a `*types.Basic`) — emits `(uint64)a`, not `((uint64)a)`. The result of a basic-typed cast can never be the receiver of a postfix `.`/`[]`/invocation (Go basic types expose no callable members and the converter emits none on them), and the C# cast operator outranks every binary operator, so the bare form binds correctly in any surrounding context: `f((uint64)a)`, `return (uint64)a;`, `(uint64)a << n`, `(nint)(uint8)k < len(s)`, and `(nint)x.Load() + 5` (which parses as `((nint)(x.Load())) + 5` — postfix `.` binds before the cast). A **named**-type target keeps the defensive outer parens `((Named)x)` — its result *can* be member-accessed (`Named(x).Method()`), which is parent-context-dependent and not decidable at the conversion site. **`string` is the exception among basic types:** its C# representation is the member-accessible golib `@string` struct, so a `string(x)` conversion *is* a valid postfix receiver — the variadic-string spread `string(r)...` → `((@string)(rune)r).ꓸꓸꓸ`, an index `string(b)[i]` → `((@string)b)[i]`, or `len(string(b))`. Dropping the wrap there reparses the postfix against the cast's inner operand (`(@string)(rune)r.ꓸꓸꓸ` binds `.ꓸꓸꓸ` to `r`, CS1061), so a `string` target retains the outer parens like a named type. (`unsafe.Pointer` stays in the no-wrap set: although its C# `@unsafe.Pointer` is a struct, Go exposes no members on `unsafe.Pointer` and the converter never emits a postfix on such a conversion result.)
* **Identity conversions are not double-cast — EXCEPT a plain constant argument.** `arenaIdx(x)` where `x` is already `arenaIdx` is a Go no-op. This arises for an untyped-constant shift that adopts the target type from context (`arenaIdx(1 << bits)`, whose operand go/types already types as `arenaIdx`, so the inner conversion has already emitted `(arenaIdx)((nuint)1 << bits)`), and for a plain `arenaIdx(yArenaIdx)`. Wrapping the already-typed expression in a second `(arenaIdx)` cast just doubles it, so the converted argument is returned as-is. The exception: a plain **constant** argument (`Word(1)`) — go/types types the constant AS the target (identity), but the render is the bare literal, which under a binary operator resolves as `int` and degrades the whole expression (math/big's `mask := Word(1)<<s - 1`, CS0029). The named cast is re-imposed at the conversion site — `((Word)1) << (int)(s)) - 1` — which also made the older `:=`-declaration-only patch in visitAssignStmt redundant (it now sees the cast already present).

**Named-numeric wrappers carry the full INTEGER operator surface.** The generated `[GoType("num:…")]` wrapper defines `+ - * / % ++ --` returning the wrapper; integer underlyings additionally define `~`, the shifts `<< >>` (int count), and the binary bitwise `& | ^` — all returning the WRAPPER type, exactly Go's typing (`Word >> ŝ` IS a `Word`). Without them C# resolved compound expressions through the implicit-to-underlying conversion and the whole expression degraded to the raw numeric (math/big's Word arithmetic, CS0266 ×45). Floats/complex omit the integer-only operators.

**Named SLICE types keep the named type when sliced.** The generated slice wrapper's Range indexer and `Slice()` overloads return the wrapper (`nat[a:b]` IS a `nat` — a fresh wrapper sharing the same backing window), so a method call directly on a slice expression binds the named type's extensions (`u[s:].norm()` bound the raw `slice<Word>` instead, math/big CS1929 ×21). The explicit `ISlice<T>` implementations keep the raw slice type.

A conversion **between two named slice types** sharing an identical underlying (tar's `sparseElem(s[i*24:])`, both `[]byte`) hops through the shared underlying slice — `((sparseElem)(slice<byte>)(…))` — since the wrapper-returning slicing makes the argument the NAMED wrapper and a direct cast would chain two user-defined operators (CS0030). (Guarded by `SortArrayType`'s `Roster(byAge[0:2])`.) The same hop covers the map and array underlyings (net/mail's `textproto.MIMEHeader(h)`, where `Header` and `MIMEHeader` are both written over `map[string][]string`).

**…but NOT when one of the two was written directly over the other.** Go's `Underlying()` resolves through the whole declaration chain, so `type shuffledFS MapFS` — testing/fstest's own suite, over a `MapFS` that is itself `map[string]*MapFile` — passes the shared-underlying test above while being a completely different shape. The wrapper for a non-basic underlying keeps the **NAMED** base (`[GoType("global::go.testing.fstest_package.MapFS")]`, see `visitIdent`/`visitTypeSpec`), so `shuffledFS` declares exactly ONE conversion operator and it targets `MapFS`. Hopping through the raw map therefore *creates* the two-operator chain the hop exists to prevent — `shuffledFS`→`MapFS`→`map`, CS0030 — where the plain cast Go actually wrote binds in one step:

```go
f, err := MapFS(fsys).Open(name)          // fsys is shuffledFS
```
```csharp
var (f, err) = ((global::go.testing.fstest_package.MapFS)fsys).Open(name);
```

The written right-hand side is recovered from `packageTypeSpecRHS` (`writtenUnderlyingOperations.go`), the same per-package pre-pass the named-**numeric** hop already consults for its own version of this exception — with one difference: the numeric exception is gated to a CROSS-package base, because a same-package numeric chain resolves to the basic underlying (`[GoType("num:uintptr")]`, no named-base operator to bind). A composite underlying keeps the named base either way, so the composite exception needs no package gate. Both directions are covered (the arg written over the target, and the target written over the arg), and all three composite arms — map, slice and array. Unrecorded or cross-package declarations miss the lookup and keep the pre-existing route.

Measured on `testing/fstest`, whose whole 7-verdict suite sat behind this one CS0030: it now runs at **6 of 7**, the residual being `TestShuffledFS`'s runtime assertion that the returned `*shuffledFile` satisfies `fs.ReadDirFile` — the pointer-adapter identity class, unrelated. (Guarded by the `DefinedOverNamedComposite` behavioral test: a child package owning the named map/slice/array, defined types over each in both directions plus a same-package one, and the net/mail two-raw-map control in the same program so the hop is proven to still fire.)

> A defined type over a named COMPOSITE gets an inherited wrapper that does not expose the golib sequence surface, so `len(x)` directly on one is CS0315 against `builtin.len<TSeq>`. That is a separate, pre-existing gap — no corpus site asks for it, and the guard above deliberately measures through the base type instead.

(Guarded by `NamedNumericConversion`, `NamedNumericShiftConv`, `NamedTypeBitwiseConst`, `IotaEnum`, `FuncTypeParam`, and `CrossPkgUser`; the `string`-target exception is guarded by `StringConvPostfix` and `UnsafeOperations`; verified by the full behavioral suite — output comparisons confirm the precedence is unchanged.)

**Generated conversion operators between named numerics of *different* assemblies.** The two paragraphs above are the *converter's inline* casts. Separately, when the converter sees a conversion *between two named numeric types* it records a `[assembly: GoImplicitConv<…>]` and the `ImplicitConvGenerator` emits a user-defined `implicit operator` for it. The emitted body constructs one named type from the other's underlying value: `new Target((ValueType)src.Value)`.

**`ValueType` is a CAST TARGET, and it names the constructed type's BACKING PRIMITIVE (corrected
2026-08-08).** The template applies it to `src.Value` and feeds the result to the constructed type's
constructor, and that constructor takes the primitive — so `ValueType` must be `uint32`, `nint`,
`int64`, not the wrapper. It named the **constructed type itself** until this was rooted, making the
body a round-trip through that type's own conversion operators — `new WaitStatus((WaitStatus)src.Value)`
— which compiles only while a standard EXPLICIT conversion exists between the two primitives, because
a user-defined conversion admits just one standard conversion on its input. syscall's unix flavors are
where it finally bit: `WaitStatus` is backed by `uint32` and `Signal` by `int` (`nint`), and
`uint32`→`nint` is not a standard IMPLICIT conversion (a 32-bit unsigned value does not fit a 32-bit
native int), so its reverse is not a standard explicit one, the operator is not applicable, and the
cast is **CS0030**. Windows declares `WaitStatus` a struct, so the pair is never registered there and
no corpus build reached it. All 49 of the corpus's `ValueType` records carried the constructed type's
own name, so the form was never right — only never yet fatal, because the two compensating generator
overrides below cover most of the gap. One consumer had to move with it: the `uintptr` hop read
`ValueType` as the type to CONSTRUCT (`new {valueType}(…)`) and now constructs the LH type and casts
to `ValueType`, exactly like the default body. (Guarded by `implicitConvValueType_test.go` — an
end-to-end conversion of two named numerics with different underlyings, plus a unit sweep over every
basic kind a named numeric can carry.)

When both named types live in the **same** assembly the default body is fine (e.g. runtime's
`muintptr ↔ Δhex`), but when the operator must **construct a *foreign* named numeric** — one declared
in another C# assembly — two problems appear that only manifest cross-assembly:

* A direct cast to the foreign named type has no route. `(NameOff)src.Value` where `src.Value` is `ulong` and `NameOff` (`internal/abi`) is a *different assembly* is **CS0030** — C# does not select the foreign type's `int32`-based user conversion for a `ulong` source across the assembly boundary (the same cast to a *local* named type compiles). It must go **through the foreign type's underlying basic**: `new …NameOff((int)src.Value)`.
* The default host can be a phantom. The operator is hosted in `partial struct {sourceType}`; if that source is the *foreign* type (reached here via a local alias, e.g. runtime's `global using nameOff = abi.NameOff`, so the cross-package dot is hidden and the conversion records as `Inverted`), the `partial struct NameOff` declares a new *empty local* type rather than extending the foreign one — **CS1729** (no constructor). The operator is relocated into the **local** type instead.

So for a foreign *constructed* type the generator emits, fully-qualified and hosted in the local type:

```csharp
// runtime, dur↔hex style: foreign abi.NameOff constructed from local Δhex
partial struct Δhex {
    public static implicit operator global::go.@internal.abi_package.NameOff(global::go.runtime_package.Δhex src)
        => new global::go.@internal.abi_package.NameOff((int)src.Value); // through the underlying int32
}
```

The override fires **only** when the `new`-constructed side (the LH type: the *source* when the conversion is `Inverted`, else the *target*) is foreign; same-assembly operators are emitted byte-identically as before (no churn). Because the trigger is inherently cross-assembly, the behavioral-test harness (single-assembly, and unable to import a foreign named numeric — `internal/*` types are un-importable from a test module and the baseline stubs expose none) cannot host it; the guard is the **`core/runtime` build**, where `NameOff`/`TypeOff`/`TextOff` ↔ `Δhex` naturally occur (this fix cleared 3×CS0030 + 3×CS1729 there).

A **same-assembly** pair also needs the through-underlying routing when the two named numerics have **incompatible underlyings** — internal/trace's public `type Time int64` ↔ unexported `type timestamp uint64`, converted both ways (`Time(ev.Ts)` / `timestamp(ts)`). The default `new Time((ΔTime)src.Value)` casts `src.Value` (a `ulong`, since `timestamp` is `uint64`-backed) straight to the wrapper, which routes through the wrapper's `long`-based user conversion — but `ulong`→`long` is not an implicit C# conversion, so the cast is **CS0030**. (This is the *mixed-accessibility* case: `Time` is exported and `timestamp` is not, so the operator is already relocated into the less-accessible `timestamp` struct — orthogonal to the underlying.) The generator now, for a **local** numeric pair, casts through the constructed type's underlying C# keyword when the source underlying does **not** implicitly convert to it: `new Time((long)src.Value)`, `new timestamp((ulong)src.Value)`. The source/constructed underlyings are read from each side's `[GoType("num:X")]` tag (a sibling generator cannot see the generated `Value` property), and the implicit-convertibility test is the fixed C# numeric-conversion table over the fixed-width integer/float basics. Crucially this fires **only** on pairs the default cast could not compile (the default `(Wrapper)src.Value` succeeds *iff* that same source→underlying conversion is implicit), so every already-compiling conversion stays byte-identical — the full behavioral suite's [goldens](Glossary.md#golden) are unchanged. `uintptr`-backed pairs keep the existing `nuint`-hop override; `int`/`uint` native-width wrappers are deliberately left to the default (their classification is version-sensitive and the failing corpus cases are fixed-width). (Guarded by the `NamedIntSignednessConv` behavioral test — a public `int64` ↔ unexported `uint64` named pair converted both ways, including a `^uint64(0)`→`int64` case whose `-1` result verifies the cast preserves the bit pattern exactly, output-compared vs Go; internal/trace's `timestamp`→`Time` inverse operator relies on it.)

A **cross-assembly** mixed-accessibility pair has no legal form at all, and is skipped. The relocation
above is the only remedy for a mixed pair — a C# user-defined conversion operator is necessarily
`public` **and** must be declared in one of its two operand types — and a *foreign* type cannot host
anything. Hosting in the local, more accessible side then exposes a type less accessible than the
operator: **CS0056** when the foreign side is the return type, **CS0057** when it is the parameter, so
neither direction is expressible. The shape is reachable only under the `-tests` white-box model, where
a package's own `_test.go` declares an EXPORTED defined type over an UNEXPORTED production one — `time`'s
`export_test.go` has `type RuleKind int` beside `zoneinfo.go`'s `type ruleKind int`, which become
`public RuleKind` in the test assembly and `internal ruleKind` in the referenced production assembly.
Nothing is lost by skipping: the converter renders such a conversion site as an explicit
through-underlying cast (`(RuleKind)(nint)r.kind`), which needs no operator at all. The local side's
accessibility comes from the GO export rule, as in the relocation above (at analysis time the `[GoType]`
partials are modifier-less); the FOREIGN side is read from metadata, where it is already final.

The recorded `GoImplicitConv` must also be able to **name the foreign type**. The recorded type name carries the foreign package's import qualifier — the DOT form `driver.IsolationLevel` for an unrenamed type, or a `ꓸ` global-using alias (`CrossPkgLibꓸGrade`) for a `Δ`-renamed one — but the attribute sits in `package_info.cs` at file scope and the generated operator lands in a `.g.cs`, neither of which carries the body files' import `using`s. A `Δ`-renamed foreign numeric resolves through its own `ꓸ` global using, but the **dot form needs a resolving `using driver = go.database.sql.driver_package;`** in `package_info.cs`'s `ImportedTypeAliases` block. The STRUCT-conversion branch of `checkForImplicitConversion` already drives that using by calling `recordConversionPackageUsing(argType)`/`(funcType)`, but the **aliased-NUMERIC branch omitted it** — so a cross-package named-numeric conversion (database/sql's `driver.IsolationLevel(opts.Isolation)`, where `sql.IsolationLevel` and `driver.IsolationLevel` are distinct named ints) left `driver` unresolved in both the attribute and the generated operator (CS0246). The numeric branch now records the same package usings. (Guarded by an extension to the `CrossPkgUser` cross-assembly test — a local `float64`-based named numeric converted to the *unrenamed* `CrossPkgLib.Celsius`, which renders in dot form and so needs the registered using; a `Δ`-renamed target like `CrossPkgLib.Grade` would have resolved via its alias and would not have caught the gap.)

The same underlying routing applies when an untyped-constant **shift** is re-typed to a named numeric. An untyped shift `1 << k` is re-typed to the type it assumes from context (so it can combine with typed operands); when that resolved type is a *named* numeric, the re-type must go through the underlying — `(arenaIdx)((nuint)1 << k)`, not a bare `(arenaIdx)(1 << k)` (CS0030). The shift's *width* is likewise decided by the underlying (a `nuint`/`uint64`-backed named type shifts the left operand in that width to avoid the `int`-overflow seen for `1 << 63`). Non-named shifts are unchanged. (Guarded by the `NamedNumericShiftConv` behavioral test — wide `uint`/`uint64`-backed and narrow `uint8`-backed named types; runtime hits this on `arenaIdx(1 << arenaBits)`.)

The unsigned named-numeric path above gets a width-cast operand, but a **signed** constant operator expression whose target is a plain builtin `int64` has no such cast, so C# would compute it in `int32` and overflow at compile time in checked mode (CS0220): `int64(1<<63 - 1)`, `var d int64 = 1<<40 + 7`, or `12345 * 1000000000 + 54321` passed to an `int64` parameter. Go evaluates each as a constant in its `int64` type. For a signed constant binary/shift expression whose folded value is **outside the C# `int32` range**, the converter emits the **folded 64-bit literal** (`9223372036854775807L`, `1099511627783L`, `12345000054321L`) instead of the operator form — correct, and self-contained. In-range constants are unchanged (they keep the readable `1 << k` form). (Guarded by the `UntypedConstArithmetic` behavioral test; runtime hits this in `mgcmark`/`netpoll`/`runtime1`.)

A signed fold whose resolved type is Go **`int`** (C# `nint`) additionally carries its own cast (2026-07-17): C# has no implicit `long`→`nint` conversion, so the bare `L` fold failed loudly at every **non-assignment** use — strings' SplitN test table puts `math.MaxInt / 4` in an `n int` struct field, and the composite-literal element emitted `2305843009213693951L` against the `nint` field (CS1503; the Phase-4 blocker-map row B7a, one site each in strings and bytes). The fold now emits `(nint)(2305843009213693951L)` — the *parenthesized* cast form the assignment path's `nativeIntConstCastType` already recognizes (`wholeExprIsCastOfType`), so assignments that previously received the whole-RHS wrap render **byte-identically** (the cast simply moves into the fold; `NativeIntWideConstAssign`'s `n = (nint)(144115188075855772L)` is unchanged). The value always fits — `nint` is 64-bit on all supported platforms — and the conversion is a runtime unchecked narrowing, never a C# constant expression, so no checked-context overflow arises. An *untyped*-int subtree keeps the bare `L` form (its enclosing context supplies the conversion), as does an `int64` target (`long` is already exact). (Guarded by the `NativeIntWideConstElement` behavioral test — composite-literal elements, call arguments, and a var initializer, values verified vs Go.)

The **in-range widened** sibling (2026-07-17; sort's test-suite conversion): a typed-`int` constant operator expression whose *value* fits int32 but whose emitted arithmetic an operand fold has widened to `long` — `maxswap: 1<<31 - 1` (sort_test `countOps`): the whole value (2147483647) is in range, so no whole-expression fold applies, but the *untyped* inner shift folds to a bare `2147483648L`, making the rendering `2147483648L - 1` — a C# `long` with no implicit conversion to the `nint` composite field (CS1503; assignments were equally unprotected, since `nativeIntConstCastType` also requires the whole value out of int32 range). `convBinaryExpr` now wraps such an expression in the same parenthesized cast at its own emission: `(nint)(2147483648L - 1)`, position-independent. The trigger is **shape-restricted** (`operandRendersWidenedFold`): an operand must be an *operator subtree* whose `overflowingConstLiteral` fold is non-empty — a named untyped-const *reference* of the same value renders as its `Untyped*` wrapper, which narrows itself at the use site (`maxInt - maxInt` stays unwrapped; wrapping it would churn green emissions). (Guarded by the `NativeIntWideConstElement` extension — the `1<<31 - 1` element, call argument, and assignment, plus the wrapper-operand control, values vs Go.)

**FLOAT literals in INTEGER contexts** render their integer form (2026-07-17; sort's test-suite conversion). A float literal *directly* typed integer by go/types has always folded (`math.Inf(1.0)` → `1` — the convBasicLit integer-form rule), but inside a constant operator expression the literal stays **untyped float** (go/types resolves the context on the outermost node only): search_test's tests table writes `{"descending 7", 1e9, …, 1e9 - 7}` against `n, i int` fields, and the element rendered `1e9D - 7` — a C# `double` against the `nint` field (CS1503). Integer contexts now **propagate** through `markUntypedConstContexts` exactly like float/complex ones, and a float literal whose propagated context is integer emits its exact integer form: `1000000000 - 7` — the arithmetic stays exact C# `int`, implicitly convertible everywhere. Two soundness gates: a non-integral literal (`1.5`) keeps its loud `D` form (`constant.ToInt` exactness), and **division does not propagate** an integer context — Go evaluates an untyped-float constant `/` in exact rational arithmetic, so a nested quotient may be transiently non-integral (`3.0 / 2 * 2` = 3) where folded operands would int-divide (`3/2*2` = 2), a silently wrong value; those trees keep the loud `double` rendering. (Guarded by the `NativeIntWideConstElement` extension — `1e9 - 7` and `5e8 * 2` elements and a `2e9 - 8` call argument, values vs Go.)

**UNSIGNED** constant expressions fold under a much narrower trigger (2026-07-03): every other unsigned shape already has a working mechanism — a *typed* unsigned shift gets the width-cast operand (`(uint64)1 << 40`), an int64-range untyped subtree is folded by the signed arm when recursion reaches it (`(281474976710655L) + arenaBaseOffset` in runtime `mranges`), and a named-const reference renders via its `Untyped*` wrapper (`(uintptr)m5 ^ 4` in runtime `hash64`). The one unfixable shape is an untyped constant **operator** subtree (a BinaryExpr) whose value exceeds **int64 entirely**: `1<<63` nested inside `(1 << 63) - 1` — go/types lands the uint64 conversion on the outermost constant node, so the inner shift stays untyped, no width cast reaches it, and C# computes it in int32. `int64((1 << 63) - 1 - (1<<63)%uint64(n))` (math/rand `Int63n`, CS0220) emits as `(int64)(9223372036854775807UL - (((uint64)1 << (int)(63))) % (uint64)n)`: the constant subtree folds to `UL`, the standalone *typed* shift keeps its readable width-cast form. Gated to plain-`uint64` underlying targets (`constExprHasBeyondInt64UntypedOperatorSubexpr`) — a native-width `uintptr` target would need a further cast the fold cannot safely synthesize, so that pre-existing caveat keeps its visible error. A first broader cut (any untyped subtree beyond int32, any unsigned target) regressed runtime's `hash64`/`mranges` by stealing exactly those already-working shapes — the narrow trigger is load-bearing. (Guarded by the `UntypedConstArithmetic` extension — the Int63n shape, value-compared vs Go.)

**`uintptr` was missing from the width-cast retype the paragraph above relies on** (2026-08-09). Everything there is conditioned on "a *typed* unsigned shift gets the width-cast operand (`(uint64)1 << 40`)", which is emitted by the shift retype in `convBinaryExpr` when `isWideShiftType` says the target does not promote to `int`. That predicate listed `uint32`/`uint64`/`int64`/`nuint` — and `uintptr` is the one wide unsigned type that does NOT render as a C# primitive: Go's `uint` becomes `nuint`, but Go's `uintptr` becomes golib's `uintptr` STRUCT. So it fell to the narrow arm, which casts the **result** — precisely the thing the function's own comment says does not help, because the shift has already happened in `int32`. `1 << (4 * goarch.PtrSize)` emitted `(uintptr)(1 << (int)(32))`, C# masked the count to five bits, and the value was **1**. golib's `uintptr` carries a native `nuint` and declares `operator <<(uintptr, int)` over it, so a cast OPERAND (`((uintptr)1 << (int)(32))`) shifts at 64 bits exactly as `nuint` does; `uintptr` simply joins the list. Whole-corpus A/B: **eight files, one mechanical family**, six of them already-correct sub-int32 values reshaped (`16 << 10`, `512 << 20`, `1 << 16`, `1 << 20`) and **two live wrong answers** — `runtime/internal/math`'s `MulUintptr` overflow fast path, whose guard read 1 instead of 2³² so every `uintptr` below `MaxUint32` "overflowed", and `runtime/mpagealloc_64bit.go`'s `1 << heapAddrBits`, which computed 2¹⁶ where Go computes 2⁴⁸. The neighbouring `1<<(UintptrSize/2) - 1` was always right, which is what hid this: there the shift is an INNER node still typed `untyped int`, so the signed fold above takes it whole. CNR is byte-identical across all 576 behavioral packages — no behavioral project had the shape until now. (Guarded by the extended `LargeUintptrConst` behavioral test, already the `MaxUintptr` pattern's home: a context-typed `1 << (4 * ptrSize)`, a literal-count `1 << 40`, and the composite-literal table row beside its always-correct `- 1` sibling, values vs `go run`; and by `runtime/internal/math`'s banked suite.)

**FLOAT** contexts need the same fold, and there the damage is **silent** rather than a compile error (2026-07-17). C# masks a shift count to the left operand's width (5 bits for `int`), so an integer-literal constant in a float context — where no arm above applies, because the constant's type is not an integer — evaluates in int32 and *quietly* yields the wrong number: `var hf float64 = 1 << 63` emitted `(1 << (int)(63))`, i.e. 63 & 31 = 31 → `int.MinValue`, and `hf / (1 << 60)` divided by 2^28 (60 & 31 = 28) instead of 2^60, printing 34359738368 where Go prints 8. Go evaluates the constant in exact arithmetic and converts the *result* to the float type, so the converter emits the Go-evaluated value as a float literal — `float64 hf = 9223372036854775808D`, `hf / (1152921504606846976D)`, `float32 sf = 1099511627776F` — which also carries the values `1<<63` puts beyond `int64`, where no `L`/`UL` fold could reach. Two gates keep the readable operator form everywhere it is already correct: the operands must be **all integer literals** (that is what makes C# evaluate in int32 — a float-literal operand like `1e18 * 10.0` already computes in `double`, and a named-const operand renders via its `Untyped*` wrapper), and the value must be **outside int32** (`1 << 10` computes identically in C# and is left alone). Unlike the int64 case, an inner shift is *not* rescued by recursion: Go promotes the operands of `1<<40 * 1.5` to a common kind, so the shift is recorded `untyped float` — invisible to the signed arm's integer test — and folds from its propagated context instead (see `markUntypedConstContexts` under [Constant Values](#constant-values)); left bare it masks to 256 and silently yields 0.375. The full-stdlib A/B footprint was exactly six lines, every one a live wrong-value bug: `math`'s `normalize` (`x * (1<<52)` off by 2^32), `cbrt` (2^54), `ldexp`'s denormal factor (`1.0/(1<<53)`), `pow`'s `1<<53`/`1<<63` branch guards, and **both** `math/rand` `Float64`s — v1 divided by `int.MinValue` and so returned *negative* numbers, v2 divided by 2^21 instead of 2^53. (`floatContextConstLiteral`, `convBinaryExpr.go`; guarded by the `UntypedConstArithmetic` extension — the `1<<63`/`1<<60` float64 and `1<<40` float32 folds, the `untyped float` nested shift, plus in-range and float-literal controls that must keep their operator form, values vs Go.)

**The same fold covers a complex128 context (2026-07-18).** `complex128` is float64-backed (`System.Numerics.Complex`), so an all-integer-literal shift whose *result* type is `complex128` — a slice/array element like `[]complex128{1 << 35, 1 << 240}` (`math/cmplx`'s `hugeIn` test inputs) — carries the identical int32-masking hazard: `1 << 35` emitted `(1 << (int)(35))`, which C# masks to 35 & 31 = 3 → **8** instead of 2^35, silently corrupting the complex value's real part (its imaginary part is 0, so it is not int-literal arithmetic). `floatContextConstLiteral` takes the constant's **real part** and folds it to a `D`-suffixed literal — `34359738368D`, and the 73-digit exact form of `1<<240` — which C# parses to the same float64 the Go constant rounds to (a power of two lands exactly; a mixed value like `1234567891234567 << 40` round-trips to the nearest double, matching Go). `complex64` is deliberately excluded: its float32 real part would overflow to a C# compile error for the beyond-float32 magnitudes this fold targets, and such constants do not arise. This cleared `math/cmplx`'s `TestTanHuge`, whose huge `Tan` inputs were being reduced to tiny masked values (8, 65536, 4096) — `Tan` then computed correctly on the *wrong* arguments. (Guarded by the `ComplexConstContext` behavioral test — `1<<35`/`1<<240`/`-1<<120`/`1234567891234567<<40` complex128 real parts, values vs Go.)

The same coercion is needed where the converter itself inserts a C# `(int)` cast on a named-numeric value — a **slice bound** (`summary[sc+1:ec]` with `sc`/`ec` of type `chunkIdx`), a **shift count** (`1 << (d % 64)` with `d` of type `statDep`), or the **length of an `unsafe.Pointer`-to-array slice** (`(*[N]T)(ptr)[:n]` → `new slice<T>(new ReadOnlySpan<T>(ptr, (int)n))`, since the `ReadOnlySpan<T>` constructor takes a C# `int` — see *Slicing a pointer-to-array*). A bare `(int)(sc + 1)` is CS0030 for the same reason, so the converter emits `(int)(nuint)(sc + 1)` / `(int)(nint)(d % 64)` — through the named type's underlying basic; a plain `nint`/`nuint` length is narrowed `(int)(n)`. Plain basic operands keep the bare `(int)(x)` form. (Guarded by the `NamedNumericIntCast` behavioral test; the Span length by `StdLibInternalAbi`.)

**Defined types over a struct — forwarded fields.** A Go type definition over a *struct* — `type winlibcall libcall` — makes the underlying struct's fields accessible on the named type (`w.fn`), without promoting its methods. The named type is emitted as `[GoType("libcall")] partial struct winlibcall;` and the `TypeGenerator` wraps the underlying value (`private libcall m_value;`). For the underlying's fields to be reachable, the generator **forwards each as a ref-returning property** over `m_value`:
```csharp
private libcall m_value;                 // NOT readonly — see below
[UnscopedRef] public ref nuint fn => ref m_value.fn;
[UnscopedRef] public ref nuint n  => ref m_value.n;
// … args, r1, r2, err
```
The underlying struct is resolved with `GetStructDeclaration` (same package, or a *source*-referenced package), and its members come from `GetStructMembers`. Crucially `m_value` is **mutable** (not the wrapper's usual `readonly`), so a write through a pointer — `c.Value.fn = fn`, where `c` is a `ж<winlibcall>` and `c.Value` is `ref winlibcall` — reaches the real storage and persists. (The `readonly`→mutable choice is decoupled from the nullable-`m_value` form that only the lazily-allocated `array` backing needs.) Forwarding is skipped for a non-struct underlying (a named type over an interface or another named type) and for an underlying that contributes no fields, so those wrappers are unchanged. *Composite-literal construction* of such a type (`winlibcall{fn: x}`) is a separate, not-yet-handled case (the runtime accesses these only by field). (Guarded by the `NamedTypeOverStruct` behavioral test — write-through and read-back of forwarded fields through a pointer; runtime hits this on `winlibcall` over `libcall`, `syscall_windows.go`.)

**The forwarded member must be a VARIABLE, and the underlying may be METADATA-ONLY (2026-07-31).** Two independent defects in the paragraph above, both surfaced by `index/suffixarray`'s `suffixarray_test.go` — `type index Index`, where `Index` has an `ints`-typed field `sa` with `len`/`get` methods — and both fixed generally:

1. **A get/set property is not a variable.** In Go the selection *is* the underlying field, so `x.sa.len()` binds a receiver the converter emits `this ref` (every value-receiver method is a ref extension) and `&x.sa` / `x.sa.Push(…)` take its address. A get/set property yields a *value*, so all of those were **CS0206** ("a non ref-returning property or indexer may not be used as an out or ref value"). The forward is now a **ref-returning property**, which is a strict superset — `w.fn = v` still assigns (through the ref), and the variable-requiring uses now bind. `[UnscopedRef]` is what makes it legal at all: a struct member returning a ref to instance state is **CS8170** by default (the receiver could be a temporary), and the attribute states the ref's lifetime is the *receiver's* — exactly Go's guarantee, since the selection aliases the wrapper's own storage. C#'s ref-safety rules then reject at the call site precisely the cases Go also rejects (addressing a non-variable). Note the neighbouring array-view case below keeps its ensure-then-share-copy shape: its accessor must *materialize* a lazily-allocated backing first, which is a different problem than aliasing an existing field.

2. **A metadata-only underlying resolved to nothing.** `GetStructDeclaration` can only see a struct whose SOURCE is in this compilation or in a `CompilationReference`; a real MSBuild build hands a `<ProjectReference>` to the compiler as compiled *metadata*, so a defined type over a struct in **another package** forwarded no members at all and every selection on it was **CS1061**. `FindUnderlyingStructSymbol` now resolves the `[GoType("…")]` definition to its `INamedTypeSymbol` when the syntax walk misses — trying the name as written (`global::go.index.suffixarray_package.Index`, the fully-rooted form the `-tests` white-box bridge emits) and then `go.`-rooted (`time_package.Duration`, the package-alias-qualified form ordinary cross-package emission uses, is not a CLR name) — and `GetForeignStructMembers` enumerates it. Membership mirrors `StructTypeTemplate`'s metadata field scan: instance FIELDS plus the ref-returning, non-indexer PROPERTIES a referenced assembly's generated wrapper exposes for its embedded and promoted members. Visibility is decided by `Compilation.IsSymbolAccessibleWithin` rather than a public-only test, which is **Go's own rule projected into C#**: an exported field is `public` and always forwards, while an unexported one is `internal` and forwards only where C# can reach it — i.e. the friend (`InternalsVisibleTo`) test assembly, which is precisely the same-Go-package case where Go permits the selection. Ordinary cross-package wrappers over foreign structs whose fields are unexported (`type timeTime time.Time`) therefore forward nothing, exactly as Go allows nothing.

Both fixes were needed for one package: with only (2), the CS1061 wall collapsed to the board's originally-reported **CS0206** at two sites — a worked example of charter §9's root-cause layering (the first diagnostic moved rather than cleared). (Guarded by the `DefinedTypeOverForeignStruct` behavioral test — a `ptlike` sub-library supplies `Outer{Name string; In Inner}`, the parent declares `type alias ptlike.Outer` and reads a forwarded field, writes one, calls `Inner`'s value- and pointer-receiver methods *through* the forwarded field with the mutations observed afterwards, writes a nested element, converts back to the underlying, and reads the zero value — output-compared vs `go run`. It is the cross-assembly sibling of `NamedTypeOverStruct`, which covers the same-package case, and of `DefinedTypeOverPkgType`, which covers a cross-package defined type reached only by conversion, never by field.)

**Defined types over an array-backed defined type — the IArray view.** A second-level definition — `type pallocBits pageBits`, where `type pageBits [8]uint64` is itself an array-backed `[GoType]` wrapper — is `len()`'d and indexed directly in Go (runtime `mpallocbits.go`), which requires `IArray` on the **outer** wrapper (golib `len(IArray)`; CS1503 otherwise, and the named-over-array *indexing* sites in `mgcscavenge`/`proc`/`traceback` fail the same way). The generator detects this in the bare-name branch — the resolved underlying struct contributes no declared members but its own `[GoType]` definition is an array form (`[N]elem`) — and implements `IArray<elem>` on the wrapper as a **view** (`IArrayViewTypeTemplate`). Every member delegates through a private `view` accessor that first touches `m_value.Value` **on the mutable field** — materializing the underlying's *lazily-allocated* backing in the wrapper's own storage — and then returns a value copy sharing that heap `T[]`, so element refs land in the real storage. (Going through the plain copying `Value` property instead silently dropped writes on a zero-valued wrapper — the backing allocated on the copy — which is the historical `pallocBits` lost-writes trap, reproduced and pinned before the fix. A struct member cannot ref-return its own field — CS8170 — so the ensure-then-share-copy shape is the correct one; the `(pageBits)(b)` reinterpret conversions keep compiling and, once the backing exists, write through shared storage.) (Guarded by the `NamedArrayWrapper` behavioral test — `len`, index read/write, and a write via the `(*pageBits)(b)` reinterpret observed through the original, values vs Go; cleared runtime's 5 `pallocBits → IArray` CS1503 **plus a −3 CS0021 cascade** of named-over-array indexing, 86 → 74 with the `copy` overload below.)

**`copy` from a defined slice type.** `copy(dst, src)` where `src` is a *named* slice type — `type pMask []uint32`, runtime `proc.go`'s `copy(nidlepMask, idlepMask)` — cannot bind the generic `copy<T1,T2>(in slice<T1>, in slice<T2>)`: the wrapper implements `ISlice<uint32>` but *is not* a `slice<T2>`, and generic inference does not see user-defined conversions, so resolution fell onto `copy(slice<byte>, @string)` (CS1503 ×2 per call). golib adds `copy<T1, T2>(in slice<T1> dst, ISlice<T2> src)` — `T2` infers from the implemented interface — copying element-wise through the interface indexer with the same min-length/convert semantics; a genuine `slice<T>` source still binds the more-specific slice/slice overload, so existing calls are unchanged. (Guarded by the same `NamedArrayWrapper` test — `copy` count/values plus post-copy independence of source and destination, vs Go.)

The wrapper also forwards the underlying's **field-box accessors**. Taking the address of a wrapper's field — `&p.x` on a `*pinnerBits`, where `type pinnerBits gcBits` (runtime `pinner.go`) — emits the box-accessor form `Δp.of(pinnerBits.Ꮡx)`, whose owning type is the **wrapper**; without a forwarded accessor the static exists only on `gcBits` (CS0117). For every forwarded *field* (properties cannot be `ref`'d and get none, matching the plain-struct template) the generator emits the accessor as a **true ref through `m_value`** into the underlying struct's field: `public static ref uint8 Ꮡx(ref pinnerBits instance) => ref instance.m_value.x;` — a genuine ref chain into the wrapper's own storage, so a write through the resulting box persists (a copy here would silently drop writes — the trap that sank an earlier `pallocBits` forwarding attempt). Emitted only when members are forwarded, which is exactly when `m_value` is mutable. (Guarded by the `NamedTypeOverStruct` extension — `bump(&c.a)` writes through the wrapper's field address and the original observes it; cleared runtime `pinner.go`'s 3 CS0117, 89 → 86.)

**Untyped constants in a typed-element context (`append`).** Because an untyped constant renders as a bare C# `int`/`double` literal or an `Untyped*` wrapper, passing one as an `append` element to a typed slice trips C#'s overload resolution: `append<T>(ISlice, params T[])` infers `T` from the element while the `slice<T>` overloads infer `T` from the slice, so `append([]uint16, replacementChar)` (or `append(buf, 7, 8)`) would pick `slice<int>` and fail (CS0121 / CS0029). The converter therefore casts an untyped *numeric*-constant `append` element to the slice's element type, matching Go's implicit conversion:

```go
var a []uint16
a = append(a, replacementChar)   // replacementChar is an untyped const
a = append(a, 7, 8)
```
```csharp
slice<uint16> a = default!;
a = append(a, (uint16)(replacementChar));
a = append(a, (uint16)(7), (uint16)(8));
```

The same cast reaches an untyped numeric constant referenced through a **cross-package SELECTOR**.
`isUntypedNumericConstArg` had matched only a bare `*ast.Ident`, so `append([]byte, tabwriter.Escape)`
(go/printer's block builder — `tabwriter.Escape` is `const Escape = '\xff'`, rendered as a golib
`UntypedInt`) kept the ambiguity (CS0121 ×6). The gate now also inspects an `*ast.SelectorExpr`'s `Sel`
constant object, casting the element to the slice's element type: `append(block, (byte)(tabwriter.Escape))`.
That same selector gate also feeds the deferred method-value arg cast — `defer(Δsyscall.Seek,
Ꮡfd.Value.Sysfd, curoffset, (nint)(io.SeekStart), ref ᒐ)` (internal/poll `fd_windows.cs`) casts the
const to the parameter type rather than the default-type wrap — and the `regexp/syntax`
`unicode.MaxRune` append; both are equal-or-better and compile.
A same-package untyped const (a bare ident) is unchanged. (Guarded by the `CrossPkgUser` extension —
`append([]byte, CrossPkgLib.Sep)` (rune `':'`) and `append([]rune, CrossPkgLib.Precision)` (int `2`), both
cross-package untyped consts reached through a selector, output-compared vs Go; without the fix the appends
are CS0121.)

The parameter-type cast also reaches the **lambda form** of a go/defer call, not just the method-value
form. When the callee returns a value (or is a value-receiver method), `visitGoStmt`/`visitDeferStmt`
force the temp-param lambda `goǃ(ᴛ1 => f(ᴛ1), arg)` (see *A value-returning goroutine callee is wrapped
in a discarding lambda*); there the arg's C# type drives ᴛ1's inference, and the lambda body's `f(ᴛ1)`
then needs ᴛ1 to be `f`'s parameter type. An untyped numeric const otherwise took `convExprList`'s
DEFAULT-Go-type cast, so ᴛ1 inferred the default (`nint`) and `f(ᴛ1)` failed — hash/crc32's
`go MakeTable(Castagnoli)` (Castagnoli an untyped `uint32` poly) emitted `goǃ(ᴛ1 => MakeTable(ᴛ1),
(nint)Castagnoli)`, CS1503. `convCallExpr` now applies the parameter-type cast in the lambda form too, but
ONLY when the parameter differs from the const's default type (`untypedNumericConstArgDefaultType` vs the
param's underlying basic) — when they match, the existing default-cast path already yields the right type,
so overriding would only churn the golden (`(nint)x`→`(nint)(x)`). Proven zero-drift on the behavioral
corpus and the full stdlib reconvert (the fix fires only where a wider/other parameter demands it).
(Guarded by the `GoUntypedConstArg` behavioral test — `go compute(poly)` with a value-returning callee and
an untyped `uint32`-poly const, output-compared vs Go; without the fix the `goǃ` arg is `(nint)poly` and
the lambda body is CS1503.)

**A string-literal spread** — `append(b, "runtime error: "...)` (runtime `error.go`'s message builder) — renders the literal as a `"…"u8` `ReadOnlySpan<byte>`, which has no spread property (`.ꓸꓸꓸ` → CS1061). The spread emission wraps a direct string-literal source in the member-accessible `@string` — `append(b, ((@string)"runtime error: "u8).ꓸꓸꓸ)` — whose `ꓸꓸꓸ` returns the `Span<byte>` the `append<T>(slice<T>, params Span<T>)` overload binds; this is the same wrap the `string(r)...` conversion spread uses (above). A non-literal spread source (a slice, a `@string` variable) is unchanged. (Guarded by the `StringConvPostfix` extension — two literal spreads appended and value-compared vs Go.)

**A string-literal CONCAT as an object/interface vararg argument** — runtime `stack.go`'s newline+tab join in `print`'s diagnostics — needs the same u8 suppression the direct literal argument already gets, propagated INTO the `BinaryExpr`'s operands: both halves otherwise render as `"…"u8` spans, and a `ReadOnlySpan<byte>` cannot box to `object` (CS1503) nor be `+`-concatenated. The binary-expression conversion now honors an incoming `BasicLitContext.u8StringOK=false`, so the operands render as plain C# strings whose `+` and boxing are fine; the default context leaves every other path unchanged. (Guarded by the `StringConvPostfix` extension — a concat with an escape into an `fmt.Println` vararg plus a nested three-way concat, values vs Go.)

**A `[]byte("literal")` over a plain-text string literal** feeds the zero-allocation `u8` ROM span straight into the slice — `[]byte("hi")` → `slice<byte>("hi"u8)` — via golib's `slice<T>(ReadOnlySpan<T>)` factory (which copies the span into the slice's backing array), rather than routing the literal through a heap `@string` first (the older `slice<byte>((@string)"hi")` allocated an `@string` and then converted it to `byte[]`). Both the general `[]byte`/`[]rune` conversion path and the `u8` literal keep their existing forms elsewhere; only the specific plain-`[]byte`-literal case is retargeted, gated to exactly what `convBasicLit` renders as a `u8` span: a `[]rune` literal keeps `@string` (it needs `@string`'s rune decoding, not raw UTF-8 bytes); a high-`\xHH`-byte `[]byte` literal keeps the byte-array-backed `@string` (its bytes do not round-trip through `u8`); a NAMED byte-slice type (`type htmlSig []byte`) keeps its wrapper cast; and a string *variable* is already an `@string`. Not ambiguous with the array `slice<T>(T[])` builtin — a `u8` literal is a `ReadOnlySpan<byte>` (an exact match for the new overload), while an `@string` converts to `byte[]` but not to a span. (Guarded by the `StringLiteralSliceConversion` extension — plain-text, raw-backtick, and high-`\xHH`-byte `[]byte` literals plus `[]rune` and string-variable controls, output-compared vs Go; and confirmed across ~144 stdlib sites by the full reconvert.)

Typed arguments and already-explicitly-converted elements (`uint16(r)`) are left as-is.

Relatedly, when the shifted (left) operand of a shift is an untyped constant — `1 << k` — Go gives the whole shift the type it assumes from context (e.g. `uintptr` when compared with a `uintptr`), but the bare C# literal makes the result `int`, which then cannot compare or combine with the typed operand (CS0034). The shift result is cast to its resolved type:

```go
var u uintptr = 7
_ = u < 1<<8   // 1<<8 takes type uintptr
```
```csharp
uintptr u = 7;
_ = u < (uintptr)(1 << (int)(8));
```

The **narrow-width** flavor of the same shift-retype is a *behavioral* requirement, not just a compile fix: a sub-`int`-width left operand (`int8`/`uint8`/`int16`/`uint16`) promotes to `int` in a C# shift, so the shift computes at 32-bit width with no wraparound at the type's own width — where Go computes a shift in the operand's type. `byte(200) << 1` is 144 in Go (wraps at byte width) but 400 in the promoted C# `int` shift. The shift result is cast back to the **shift expression's resolved Go type**, so a var, *typed*-const, and *untyped*-const left operand all wrap alike (the untyped-const flavor was historically correct only via the wrapper retype above; typed left operands got no cast at all and produced the unwrapped value):

```go
var cb byte = 200
var k uint = 1
fmt.Println(cb << k)   // 144: wraps at byte width
```
```csharp
byte cb = 200;
nuint k = 1;
fmt.Println((byte)(cb << (int)(k)));
```

A whole-expression Go **constant** shift is skipped (Go constant arithmetic cannot overflow its type, and the wrap cast on a C# compile-time constant would even be rejected, CS0221), and **right** shifts take no cast (a narrow operand zero-/sign-extends into the `int`-width shift, so the result always fits the narrow width). A **named** narrow type routes through its underlying — `(nb)(byte)(n << (int)(k))` — since a `[GoType]` conversion accepts only its exact underlying, never C# `int`. `int32`-and-wider left operands already shift at their Go width in C# and keep their existing forms. (Guarded by the `NarrowShiftVarCount` behavioral test — byte/uint16/int8/int16 left shifts by variable counts that overflow the narrow width, across var, typed-const, untyped-const, and named-type left operands, plus right-shift controls, output-compared vs Go.)

A C# **compound shift-assignment** (`<<=`/`>>=`) requires the shift count to be `int`; the count's own (possibly unsigned/native-width) type is rejected — `s.allocCache >>= (nuint)x` is CS0019. So the count is cast to `int` (`s.allocCache >>= (int)x`). This applies whether the assignment target is a simple variable or a **selector/pointer-field** LHS (`s.allocCache`, a field reached through a `*mspan`) — both paths emit the same `(int)` count cast. (Guarded by the `ShiftPrecedenceUnsigned` behavioral test — simple-variable and struct-field shift-assigns with an unsigned count; runtime hits the field form in `malloc`/`mbitmap`'s `allocCache` bit walks.)

A related case is a **computed constant mask under a native-int bitwise operator**. `i & ((1 << shift) - 1)` or `i &^ (blockSize - 1)`, where `i` is a `uintptr`/`uint` (C# `nuint`/`uintptr`) and `shift`/`blockSize` are native-int constants: the mask is a Go compile-time constant, but because the native const is emitted as a get-only property (package scope) or a plain local (function scope), not a C# `const`, the *expression* is not a C# constant, so it renders as a bare `int` — and `nuint & int` is CS0019 (no common type, and no implicit constant conversion since the operand is non-constant). The converter casts such a computed-constant operand to the native result type — `(uintptr)i & (uintptr)((1 << (int)shift) - 1)`. A *small* bare literal (`x & 7`) is left alone (C#'s constant conversion fits it), but a **large** literal whose value exceeds the C# `int32` range (`uintptrMask & 0x00ffffffffff`) is emitted by `convBasicLit` with its own `(nint)`/unsigned cast — so it is no longer a bare `int` and `nuint & nint` is CS0019 too; such a literal operand is cast to the native result type the same way (`& (uintptr)(nint)1099511627775L`). A named untyped-const reference is handled by the wrapper cast below. There is also a `&^` (AND-NOT) twist: it is rendered `& ~y`, and `~` promotes its operand to `int`, so even a *small* constant operand (`p &^ 15` → `nuint & ~15` = `nuint & (int)-16`) is CS0019 — a negative `int` cannot convert to an unsigned native type, even as a constant. So a constant right operand of `&^` with a native-int result is also cast to the native type, `& ~(uintptr)15`, performing the complement in that width (a non-constant native operand, `p &^ mask`, already complements correctly and is left alone). (All guarded by the `NativeIntConstMask` behavioral test — computed mask, large-literal mask, and small-literal `&^`; runtime exercises this in arena/page mask arithmetic such as `arenaIndex`/`alignDown`, `mallocinit`'s `uintptrMask &`, and `os_windows`'s `ptr &^ 15` 16-byte align.)

Similarly, when a *named* untyped numeric constant (emitted as the `UntypedInt`/`UntypedFloat` wrapper) is an operand of arithmetic with a concrete numeric type, the wrapper's bidirectional implicit conversions can make the result resolve to the wrong type (`a * two32`, `uint64 * UntypedInt`, yields `int` — CS0029). The named-const operand is cast to the concrete operand's type (comparisons resolve through the implicit conversion, so only arithmetic is cast):

```go
const two32 = 1 << 32
var a uint64 = 100
_ = a*two32 + 3
```
```csharp
UntypedInt two32 = /* 1 << 32 */ 4294967296;
uint64 a = 100;
_ = a * (uint64)two32 + 3;
```

A constant too large for `int64`/`uint64` (or `float64`) is emitted as `GoBigConst` (=
`System.Numerics.BigInteger`), which has **no** implicit operator with the built-in numeric types.
Unlike an `UntypedInt`/`UntypedFloat` wrapper, that makes a bare reference a hard error in *every*
concrete numeric context, not merely a resolution hazard in arithmetic — so the cast belongs to the
**reference itself** (`bigIntegerConstMaterialization`), applied wherever go/types records a
concrete numeric type on it:

```go
const below1e23 = 99999999999999974834176
var ftoatests = []ftoaTest{{below1e23, 'e', 17, "9.99999999999999748e+22"}}
_ = x > Two129                     // Two129 = 1<<129
```
```csharp
internal static readonly GoBigConst below1e23 = /* 99999999999999974834176 */
    GoBigConst.Parse("99999999999999974834176");
internal static slice<ftoaTest> ftoatests = new ftoaTest[]{
    new((float64)below1e23, (rune)'e', 17, "9.99999999999999748e+22"u8)}.slice();
_ = x > (float64)Two129;
```

Comparison was originally the only casting consumer (in `convBinaryExpr`), which left composite-literal
elements, call arguments, typed `var` initializers, assignments, returns, and channel sends emitting
bare — twelve `BigInteger`→`double` CS1503s in `strconv`'s `ftoa_test.cs` alone. Moving the cast to the
reference serves all of them at once, and the comparison arm was dropped so it no longer double-casts
(`(float64)(float64)Two129`). Two properties make the context type reliable: go/types records the
**converted** type on the reference (inside `[]float64{…}` the recorded type is `float64`, not
untyped), and Go only admits a constant where its value is representable — so a BigInteger-backed
value's concrete context is necessarily float/complex, never a 64-bit integer that would overflow.
The reference is kept readable rather than folded to a literal, the same call
`foldedNamedFloatConstLiteral` makes for a bare reference. (Guarded by `BigUntypedConstComparison`,
extended from comparison-only to every position, with an in-range `UntypedInt` const as the
must-stay-uncast counter-control.)

#### An INTEGER expression over a `GoBigConst` constant folds — it has no 64-bit form
The `(float64)Two129` cast above works because BigInteger converts to `double`. An **integer** target has
no such luck: `(uint64)mask` on a 128-bit BigInteger throws `System.OverflowException` at run time. That is
not a corner case — it is the shape of the Go standard library's whole-width byte-classification bitmap
idiom (`go/doc/comment`'s `isHost`/`isPath`/`isIdentASCII`/`importPathOK`, `net/textproto`'s
`validHeaderFieldByte`/`validHeaderValueByte`), where a 128-bit untyped `mask` is legal precisely because
Go requires only the FINAL value of a constant expression to be representable:

```go
const mask = 0 | (1<<26-1)<<'A' | (1<<26-1)<<'a' | (1<<10-1)<<'0' | 1<<'_' | /* … */ 1<<':'

return ((uint64(1)<<c)&(mask&(1<<64-1)) |
	(uint64(1)<<(c-64))&(mask>>64)) != 0
```

Both halves are `uint64`-valued constants, so both must emit as the go/types-recorded **folded value**.
`mask&(1<<64-1)` already did — its `1<<64-1` operand subtree exceeds int64, which
`constExprHasBeyondInt64UntypedOperatorSubexpr` recognizes. The sibling `mask>>64` has no such subtree: its
only unrepresentable operand is the *reference*, which the shift path retyped to the shift's resolved width
(`((uint64)mask).Rsh(64)`) and threw. `overflowingConstLiteral` therefore also folds on
`constExprHasBeyondUint64UntypedConstRef` — any PROPER subexpression that is a named untyped-const
reference fitting neither int64 nor uint64 (the `GoBigConst` emission, `isBigIntegerBackedConstRef`):

```csharp
GoBigConst mask = /* 0 | (1<<26-1)<<'A' | … */ GoBigConst.Parse("10633823862292363665388054147449749504");
return ((uint64)((uint64)((((uint64)1).Lsh((uint64)(c))) & (576284830442979328UL)) |
        (uint64)((((uint64)1).Lsh((uint64)((c - 64)))) & (576460746666278911UL)))) != 0;
```

Unlike the sibling overflow folds this one is **magnitude-independent**: the operator form does not merely
compute in the wrong width, it *throws*, so a folded value that fits int32 (`mask>>64` of `1<<70 | 1<<3` is
64) folds too. Scope: the unsigned arm covers `uint64`/`nuint`/`uintptr`; the signed arm is confined to the
64-bit-wide targets its `…L` / `(nint)(…L)` contract already covers (a narrower signed target keeps the
operator form, where the wrapper cast fails LOUDLY rather than silently computing the wrong value — no such
site exists in the stdlib corpus). The `mask` local itself stays emitted, unused, carrying the gofmt'd Go
constant as its comment: it is what makes the folded magic numbers readable back to the Go source (its
`BigInteger.Parse` hoists to a static field — see the next subsection).

Corpus footprint of the fold: exactly two files across the 302-package stdlib conversion
(`go/doc/comment/parse.cs`, `net/textproto/reader.cs`), both still compiling clean.
(Guarded by the `UntypedConstWideMask` behavioral test — the `isHost` mask, the `&^`-inverted
`validHeaderValueByte` mask, a small-valued high half, and a `uintptr`-target native-width mask, all
output-compared vs Go. Without the fold the `uintptr` arm is a hard CS0030 and the `uint64` arms throw.)

### A function-LOCAL `GoBigConst` hoists its parse to a `static readonly` field

A Go constant has no runtime existence — its value lives in the instruction stream — and `GoBigConst`
is the one C# constant projection with a real **per-evaluation** cost: `BigInteger.Parse` allocates its
bits array on every run. Emitted as a plain local, that parse re-ran on **every call** of the enclosing
function; `net/textproto`'s `validHeaderFieldByte` paid it 14 times per `canonicalMIMEHeaderKey` call
(560 B against Go's 0) inside `TestCommonHeaders`' want-ZERO `testing.AllocsPerRun` assert — and the
local was not even referenced, every use having been folded by the subsection above. An **int-kind**
function-local big constant therefore hoists its parse to one `private static readonly` field above the
function (the hoisted-string-literal pattern), and the local initializes from the field — a BigInteger
struct copy, which allocates nothing:

```go
func validHeaderFieldByte(c byte) bool {
	const mask = 0 | (1<<(10)-1)<<'0' | /* … */ 1<<'~'
	…
}
```
```csharp
// Hoisted Go big-integer constant (single parse; Go folds constants at compile time)
private static readonly GoBigConst maskᶜ = GoBigConst.Parse("116972063611741436228934278030836105216");

internal static bool validHeaderFieldByte(byte c) {
    GoBigConst mask = /* 0 | (1<<(10)-1)<<'0' | … */
            maskᶜ;
    return …;
}
```

Field names are claimed package-wide (`<name>` + `HoistedConstMarker` `ᶜ` + ordinal on collision —
`reader.cs` declares `maskᶜ` and `maskᶜ1` for its two functions' masks), deterministic because files
convert sequentially; a `-tests` internal variant seeds from the production conversion's claims exactly
as lifted type names do (`productionHoistedConstOrdinals`). **Float/complex OVERFLOW constants keep the
per-call parse**: their exact string may be a rational (`"1/3"`) whose `Parse` throws, and a field
initializer would turn that per-call throw into a package-class `TypeInitializationException`.
Package-level big consts were already `static readonly` fields and are unchanged. (Guarded by
`UntypedConstWideMask` — four functions with local big-const masks, exercising the ordinal chain — and
by `net/textproto`'s validated `TestCommonHeaders`, whose want-zero assert is what surfaced the cost;
L11.)

### The `&^=` (bit-clear) compound assignment on a narrow type
C# has no `&^` (AND-NOT) operator, so Go's `a &^= b` expands to `a &= ~b`. The `~` complement always promotes its operand to `int`, and `int` is not implicitly convertible to a narrower or unsigned LHS type (`byte`/`ushort`/`uint`/`ulong`/`uintptr`/`nuint`) — so `flags &= ~b` is CS0266. The complemented value is therefore cast back to the LHS type, inside `unchecked` because for a *constant* operand `~b` folds to a negative `int` constant whose checked narrowing would overflow (CS0221):

```go
h.flags &^= hashWriting   // h.flags is uint8
```
```csharp
h.Value.flags &= unchecked((uint8)~hashWriting);
```

An LHS type that `int` widens to implicitly (`int`/`int32`/`int64`) needs no cast and stays `a &= ~b`. (Guarded by the `AndNotAssignNarrow` behavioral test, which exercises both an ident LHS and a struct-field LHS — they route through different assignment-emission paths.)

### A standalone `^x` on `uint8`/`uint16` truncates back to the operand's type

The same `int` promotion has a **silent-value** face, not just a CS0266 face. Go's `^x` has x's own
type, so on a sub-int UNSIGNED type the complement wraps to that width — `^uint16(5)` is `65530`. C#
promotes `byte`/`ushort` to `int` first, so bare `~x` is `-6`: identical in the low 16 bits, but every
**widening** use then carries the sign bits, and no cast is required to make it compile:

```go
w.writeBits(int32(^uint16(length)), 16)     // compress/flate, stored-block header
```
```csharp
w.writeBits((int32)((uint16)(~(uint16)length)), 16);
```

Without the inner truncation this wrote `-6`, and `writeBits`' `bits |= uint64(b) << nbits`
sign-extended it across the whole 64-bit accumulator — so every level-0 (`NoCompression`) DEFLATE
stream was garbage and the decoder rejected its own encoder's output with `flate: corrupt input
before offset 59`. It compiled clean and only `compress/zlib`'s `TestWriter`, which round-trips at
every level, caught it.

Only unsigned `uint8`/`uint16` need this. A **signed** narrow type is already value-correct (C#'s `~`
of a sign-extended operand equals the sign-extended Go result: `int32(^int16(5))` is `-6` in both
languages), and every type at least 32 bits wide (`uint`, `uint32`, `uint64`, `uintptr`, all signed
widths) keeps its own type under C#'s `~`. A NAMED type routes through its golib wrapper's operator.

A **CONSTANT** operand needs one more word. The truncation is then a C# *constant conversion*, and
those are checked at compile time no matter what the enclosing context says: `~(ushort)0` promotes to
the `int` `-1`, and `(ushort)(-1)` is a hard **CS0221** — however correct the runtime truncation would
be. The all-ones idiom is exactly that shape, and it is the bound `x/net/dnsmessage` compares each
section count against, seven times in one file:

```go
if len(m.Questions) > int(^uint16(0)) {     // vendor/golang.org/x/net/dns/dnsmessage
```
```csharp
if (len(m.Questions) > (nint)(unchecked((uint16)(~(uint16)0)))) {
```

So a Go-constant operand states `unchecked`; a variable operand does not, since C#'s default context
is already unchecked and the keyword would only add noise at flate's `^uint16(length)` sites.

Where the result is *immediately* narrowed back by the surrounding narrow-arithmetic cast the inner
truncation is redundant — `takeU8(^a)` renders `takeU8((uint8)((uint8)(~a)))`. That is accepted
cosmetic noise: the two forms are value-identical, and the alternative (deciding at the unary site
whether the enclosing context widens) trades a silent-corruption hole for readability. (Guarded by
the `AndNotAssignNarrow` behavioral test's widening cases — `int32(^uint16(x))`, `uint64(^uint8(x))`,
the `uint32`/`int16` no-op controls, the narrow round-trip, and the constant cases `int(^uint16(0))`,
`int(^uint8(0))` and `uint64(^seed)` over a typed `uint16` const.)

### Logical operators on a named boolean type cast through `bool`
A Go defined type whose underlying type is `bool` (`type boolVal bool`) is modeled as a `[GoType("bool")]` struct with an implicit `bool` conversion but no logical operators. Go's `!`, `&&`, and `||` on such a value yield that **same named type**, so `return !y` / `return x && y` in a function returning an interface the type implements (go/constant's `UnaryOp`/`BinaryOp`, returning the `Value` interface) still satisfies the interface. A bare `!y` / `x && y` in C# collapses to a plain `bool` — which cannot implicitly convert to the interface (CS0029), and `!` has no operator on the struct (CS0023). The converter casts each operand through `bool`, applies the operator, then casts the result back to the named type so it keeps satisfying the interface:

```go
case boolVal:
    return !y          // y is boolVal, result must be the Value interface
```
```csharp
case boolVal y: {
    return ((boolVal)(!(bool)y));
}
```

Binary `&&`/`||` take the parallel form `((boolVal)((bool)x && (bool)y))`. A predeclared-`bool` operand keeps the bare `!x` / `x && y` form (no golden churn). (Guarded by the `NamedBooleanLogic` behavioral test.)

### Casting a negative value to a non-keyword type parenthesizes the operand
C# parses `(T)-value` as a cast only when `T` is a keyword primitive (`int`, `long`, `nint`, `byte`, …). For a using-**alias** (`int64`=`long`, `uint64`=`ulong`, `rune`=`int`, …) or a `[GoType]` **named** type (`level`), `(int64)-1` / `(level)-1` is instead parsed as `type MINUS value` — CS0075 ("to cast a negative value, you must enclose the value in parentheses") and CS0119 ("'long' is a type, not valid in the given context"). So a cast whose operand leads with a unary `+`/`-` and whose target is not a C# keyword type parenthesizes the operand:

```go
lvl := level(-1)              // named conversion
mask := -1 << uint(bits)      // int64-typed wide shift
```
```csharp
var lvl = ((level)(-1));
var mask = ((int64)(-1) << (int)((nuint)bits));
```

Two emission sites carry it: the type-conversion cast (convCallExpr, `castOperandNeedsParens`) covers `level(-1)`/`int64(-1)`, and the wide-shift left-operand cast (convBinaryExpr) covers `-1 << bits` (a wide shift type does not promote to `int`, so its left operand is cast to that type). A keyword target (`(int)-1`, `(nint)-1`) and a non-negative operand keep the bare form (no golden churn). (Guarded by the `CastNegativeNamedType` and `ShiftNegativeWideConst` behavioral tests.)

### A named complex type emits only Go's complex operator set
The generated named-numeric wrapper (`go2cs-gen` `InheritedTypeTemplate`/`NumericTypeTemplate`) emits the operator surface of the *underlying kind*, and Go's complex kinds define only `==`/`!=`, `+`/`-`/`*`/`/`, unary `-`, and `++`/`--` — **no ordered comparisons and no `%`** (the Go spec limits `<`/`<=`/`>`/`>=` to ordered types and `%` to integers; C#'s `System.Numerics.Complex` and golib `complex64` have neither operator either). A `type C complex128` therefore gets no `<`/`<=`/`>`/`>=`/`%` operators and no `IComparisonOperators` interface declaration — emitting them was **CS0019 ×5 per type** (first hit: `testing/quick`'s `TestComplex64Alias`/`TestComplex128Alias`, which compile-blocked the whole quick test host). Integer named types keep the full set including `%`/bitwise/shifts, and float named types keep ordering (and C#'s native float `%`, inert for converted Go, stays). Same kind-gate shape as the pre-existing complement/shift gate (`GetComplementOperator`). Guarded by the `NamedNumericIncDec` behavioral test's named-complex block (`++`/`--`/arithmetic/equality on a `type cx complex128`).

## Floating-Point Formatting

Go's default rendering of a float — `%v`, `%g`, and the bare `Println`/`Print`/`Sprint` paths — is
`strconv.FormatFloat(f, 'g', -1, bitSize)`: the shortest decimal digits that round-trip back to the same
float, laid out in `'e'` form when the decimal exponent is **below -4 or at/above 6**, and `'f'` form
otherwise. The exponent is lowercase, always signed, and always at least two digits.

.NET's default/`"R"` formatting *also* produces shortest-round-trip digits, but presents them differently:
it flips to exponent form on its own thresholds and writes an unpadded, uppercase exponent. The two agree
far more often than they disagree, which is what makes the disagreement easy to miss — the gap only opens
at the ends:

```go
fmt.Println(1000000.0)                   // Go: 1e+06        .NET default: 1000000
fmt.Println(1e-5)                        // Go: 1e-05        .NET default: 1E-05
fmt.Println(2.2250738585072014e-308)     // Go: …e-308       .NET default: …E-308
fmt.Println(999999.0)                    // Go: 999999       .NET default: 999999   (agree)
```

The 6 is the whole story for `%v`: it is why `1000000.0` prints as `1e+06` while `999999.0` prints in full,
and it comes from strconv's `formatDigits`, which pins the exponent threshold to a flat **6 whenever the
digits were the shortest round-trip** (`if shortest { eprec = 6 }`) rather than to the requested precision.
The threshold is *not* 21 — that figure appears in an older reading of the rule and matches JavaScript's
`Number.toString`, not Go's.

**The conversion:** the baseline stub `fmt` (`src/core/fmt/format.cs`) reproduces strconv's layout rather
than delegating to .NET's presentation. It leans on an empirical equivalence, verified by differential
fuzzing (below): **.NET and Go produce the same digits**. .NET's `"R"` is the same shortest round-trip, and
its `"E<n>"` rounds the *exact* binary value to `n+1` significant digits with the same round-half-to-even
that strconv's `shouldRoundUp` applies — including denormals, and exactly (not zero-padded) well past 17
digits, so `%.40e` of `0.1` agrees digit-for-digit. Only the *presentation* differs. So `FormatFloat` takes
.NET's digits, reduces them to strconv's `decimalSlice` shape (`DecomposeDigits`: significant digits, sign
and trailing zeros stripped, scaled so the value is `0.<digits> × 10^dp`), and lays them out through direct
ports of strconv's `fmtE` and `fmtF`. Fixed-point (`%f` with a precision) is the one case handed to .NET
whole — `"F<n>"` already matches `fmtF` exactly, and it wants digits at a decimal place rather than a
significant-digit count.

Verb defaults follow Go's `fmt`: `%v` renders as `%g`, `%F` as `%f`; `%e`/`%f` default to a precision of 6
and `%v`/`%g` to the shortest round-trip; an explicit precision overrules either. `float32` resolves its
digits **as a single** (`((float)value).ToString(…)`, never widened to double first), so `float32(1.0/3.0)`
prints Go's `0.33333334` and not the double's `0.3333333333333333`. Two Go quirks that a straight reading
of the rule misses, both caught by the fuzz:

* **`%g` promotes a zero precision to one significant digit — and that promotion feeds the exponent-form
  decision too.** strconv mutates `prec` itself (`case 'g', 'G': if prec == 0 { prec = 1 }`), not merely the
  digit count, so `%.0g` of `0.7` is `0.7`. Applying the promotion only to the digit count leaves the
  threshold test comparing against 0, which rounds the value away to `0`.
* **±Inf carries an inherent sign** (`+Inf`, never `∞`), the space flag demotes its `+` to a space, and
  Inf/NaN space-pad under the `0` flag because they do not look like numbers.

**Verification.** Beyond the fixed cases, a differential fuzz compared the stub against `go run` over
random float64/float32 **bit patterns** (so NaNs, infinities and denormals arise naturally), precisions 0–20
across `%g/%e/%f/%G/%E`, and values clustered on the threshold where the form flips: ~50,000 formatted
values over six seeds, all byte-identical. The `%.0g` promotion above was found this way — the hand-picked
cases all passed without it.

**Scope.** This is the hand-written baseline **stub** `fmt`, the proxy the behavioral corpus builds against.
The full conversion's `fmt` calls the *converted* `strconv`, which is Go's own digit code and needs none of
this. (Guarded by the `FloatFormatExponent` behavioral test — both thresholds from either side, `1e20`/`1e21`,
`1000000.0`, the `math.MaxFloat64`/`SmallestNonzeroFloat64` values, float32 counterparts, negative zero, and
every verb with and without precision, byte-compared against `go run`; ±Inf/NaN flag interactions live in
`PrintfWidthFlags`. The extreme values are spelled as literals because the converted `math` package's
constants are themselves rendered lossily — `MaxFloat64` as `1.79769e+308` — which is a separate converter
defect, deliberately not conflated with this one.)

### A folded constant of a NAMED type carries its type in the fold

`overflowingConstLiteral` materializes a compile-time integer constant whose value falls outside the C#
`int32` range, because C# would otherwise evaluate the operator expression in `int32` and overflow
(CS0220). It read the constant's type through `Underlying()`, so a constant of a *defined* type folded
to a bare basic literal and the Go type was simply lost:

```go
d := 8 * time.Hour
secondsEastOfUTC := int((8 * time.Hour).Seconds())
```

```csharp
var d = 28800000000000L;                            // a C# long, not a Duration
nint secondsEastOfUTC = (nint)(28800000000000L).Seconds();   // CS1929 — long has no Seconds
```

The compile error is the loud half; the silent half is `d`, which is now a `long` and prints as its
digit count where a `Duration` prints `8h0m0s`. The fold now carries the named type in the same
parenthesized `(T)(…)` shape the native-int arm uses — `(time.Duration)(28800000000000L)` — which
`wholeExprIsCastOfType` already recognizes, so enclosing paths do not re-wrap it. The `[GoType]` wrapper
converts implicitly from its underlying, so the cast is always legal, and Go's own parentheses around a
method-call receiver keep the postfix `.M()` binding to the cast rather than to the literal. Only
constants outside `int32` reach this arm at all, so the corpus footprint is the handful of computed
`time.Duration`-class constants above that magnitude. (Guarded by the `PackageNameShadowing` behavioral
test, case 4.)

## Nil and Zero Values
In Go, `nil` is the equivalent of C# `null`. Where possible, converted code uses the golib [`NilType`](https://github.com/ritchiecarroll/go2cs/blob/master/src/core/golib/NilType.cs) with a default instance called `nil` (defined in [`go.builtin`](https://github.com/ritchiecarroll/go2cs/blob/master/src/core/golib/builtin.cs)). `NilType` provides comparison operators so `x == nil` / `x != nil` work across the runtime types (slices, maps, channels, pointers, interfaces), each of which defines what "nil" means for it (e.g. a `map<K,V>` whose backing dictionary is null is the nil map: reads return the zero value, `len` is 0, ranging yields nothing, and a write panics — matching Go).

The same null-safe-zero-value principle applies to value types whose backing store is a reference. A zero-value `string` converts to `@string s = default!`, which runs no constructor, so the backing `byte[]` is null. Rather than [NRE](Glossary.md#nre) on the first read, `@string` treats a null backing as Go's empty string `""` for every read — length 0, no bytes to index/range, `== ""` is true, prints empty, and concatenation yields the other operand (`var s string; s += "x"` → `"x"`). Constructors still allocate, so only the `default(@string)` zero value relies on this. (Guarded by the `StringZeroValueConcat` behavioral test.)

### The THREE deref accessors of `ж<T>` — when each is needed, and how the converter picks

Establishing a local `ref` over a heap box (`ref var p = ref Ꮡp.<accessor>`) looks like one
operation but encodes different answers to one question: **is this access the Go DEREFERENCE, and
what does Go say happens on nil at exactly this point?** Consolidated here because the members
landed across separate arcs (their individual sections, linked below, carry the full derivations);
this is the map.

| Accessor | On nil | The Go semantics it encodes | How the converter KNOWS |
|:--|:--|:--|:--|
| `.Value` | **panics immediately** (Go's message, even on bind) | this access IS the deref, and Go panics here — the ordinary pointer USE site (`*p`, `~Ꮡp`, a read through the box) | the DEFAULT everywhere except a pointer's ENTRY alias; no special case applies |
| `.ValueSlot` | **no check** — the slot as-is | a read of the HELD value, never a deref: when the pointee is itself reference-like, `*p` legally yields nil (`*(&err)` of a nil `error` panics in neither language), so `.Value`'s null check would fire SPURIOUSLY on a legally-held null. Identical to `.Value`'s slot in every non-throwing case. Also where nil is structurally impossible (a freshly `make`-allocated box, `heap(out …)`) and in the reflection bridge's field paths. | by the POINTEE'S TYPE or by CONSTRUCTION — a box-of-pointer LOCAL, a named-result box, the bridge's field walk. NOT at a pointer's entry alias (see below) |
| `.DerefOrNull()` | **defers** — binds `Unsafe.NullRef<T>`, faults with Go's panic on first USE | Go defers the panic to the body's own deref point: passing a nil `*T` to a function, or calling a method through one, is legal; the body RUNS, a side effect before the deref must happen, and the panic lands where Go's would — after it, or never (delegated `checkValid`-style guards). | STRUCTURALLY — EVERY direct-ж pointer ENTRY alias, RECEIVER and PARAMETER alike, unconditionally (no analysis, because the accessor is faithful whether or not the body guards), plus the pointer-reassignment re-alias and go2cs-gen's `ReceiverMethodTemplate` bridge; see *A nil RECEIVER is nil-deferring, not nil-safe* and *A pointer PARAMETER is nil-deferring for exactly the reason a receiver is* |

Why three and not one: the ENTRY alias and the USE site are different questions, and `.Value`
answers the second. `.ValueSlot` is different in KIND rather than in timing — it marks accesses
that were never dereferences in Go's semantics at all, which no nil-policy accessor can express —
but it is not selected at an entry alias, where nothing can know whether the body will dereference
and the nil-policy question is the only one being asked.

**There used to be a fourth, `.DerefOrNil()` — a nil-SAFE accessor handing back a shared
`default(T)` slot — and its retirement (2026-08-02) is what collapsed the set.** It was admitted
by a body ANALYSIS: a pointer param the body nil-compares, one passed the untyped `nil` at a
same-package call site, or one whose first mentioning statement re-points it without dereferencing
(`l = l.get()` normalization). Wherever that analysis was RIGHT the silent zero was unobservable;
wherever it was wrong — and it could never be complete, because a body's guard may be DELEGATED to
a callee it merely hands the pointer to — a deref Go says must panic instead read a silent zero.
Unifying every pointer entry alias on `.DerefOrNull()` made the analysis unnecessary in the first
place, so the accessor, the three analyses that fed it (`collectNilSafePtrParams`,
`reassignedBeforeDerefParamName`, and the package-wide nil-argument pre-pass) and their vestigial
receiver arms were deleted together — 382 net lines of converter. The golib method survives with
its own unit coverage, but converted code no longer emits it.

### Canonical typed-nil pointer boxing
Go's typed nil is a real value: `any((*T)(nil))` is a **non-nil** interface carrying dynamic type
`*T`, `%T` prints `*T`, and the pervasive descriptor idiom `reflect.TypeOf((*T)(nil)).Elem()`
resolves the pointee type. A bare C# `null` erases all of that, so a nil→pointer **conversion**
renders in pointer context and yields the type's **canonical typed nil instance** — one shared,
write-protected instance per pointer type (`ж<T>.NilBox`; a generated named-pointer wrapper's
`NilInstance`), which the `NilType` implicit conversion returns:

```go
var errorType = reflectlite.TypeOf((*error)(nil)).Elem()
var x any = (*int)(nil)   // x != nil; %T prints *int
```
```csharp
internal static reflectliteꓸType errorType = reflectlite.TypeOf(((ж<error>)nil)).Elem();
any x = ((ж<nint>)nil);
```

Supporting semantics, all structural (`m_isNull` — never the value-peeking `IsNull`, which remains
the dereference guard):

- **Pointer identity**: a null reference and a nil box are the same Go nil pointer and compare
  equal; a heap box *holding* a nil value (`ж<ж<T>>` captured local) is a **non-nil pointer holding
  nil** — two such distinct addresses are unequal and neither equals nil (`&p1 != &p2` though
  `p1 == p2 == nil`), including when both slots share the canonical singleton.
- **Interface adapters** null-coalesce their receiver box to the canonical instance, so an
  interface holding a nil `*T` keeps its dynamic type, typed nils compare correctly across the
  `any`/interface boundary (`(*A)(nil)`-error ≠ `(*B)(nil)`-error), and `case *T:` matches with a
  nil pointee.
- The non-generic `INilPointer` surface exposes the structural predicate to runtime machinery
  holding a pointer only as `object` (equality tails, the reflection bridge's `IsNil`/`Elem`).

**Every consumer that asks "is this THE nil pointer" must ask the structural predicate.** The
managed-slot `atomic.Pointer<T>` (`core/sync/atomic/type.cs`) canonicalizes the nil pointer to a null
slot so a reference `CompareAndSwap` treats all nil `*T` values as equal — and its `nilCanon` helper
asked the value-peeking `IsNull`, so it collapsed a *pointer to a nil value* to nil as well. `sync.Map`
is built out of exactly that shape and lost both halves of it: `e.p.Store(&i)` with a nil `any` value
dropped the entry outright (`load()`'s `p == nil` then reported not-ok, so `Range` skipped it and
`CompareAndSwap` failed against it), and the `expunged = new(any)` sentinel — a real address holding a
nil interface — became indistinguishable from nil, so a *deleted* entry could not be told from an
*expunged* one and the whole dirty/expunge protocol degenerated. The predicate is now
`ж<T>.IsNilPointer`. The same conflation applied to `atomic.Pointer[error]`, `atomic.Pointer[func()]`
and any `**T` slot (`atomic.Pointer[*T]`), all present in the corpus. (Guarded by
`AtomicPointerToNil`: `Load`/`Store`/`Swap`/`CompareAndSwap` over a pointer to a nil `any`, two
distinct `new(any)` sentinels, a pointer to a nil `*int`, and the genuinely-nil slot, output-compared
vs `go run`. Before the fix the guard panics with a nil-pointer dereference on its second line.)

`(*T)(nil)` conversion **expressions** are where the canonical instance is *minted*, and pointer
locals, parameters and fields keep plain `null` — their statically-typed world never needs the
type carried. The type is carried where it becomes observable instead: at the **boundary into
interface space** (below).

A target written as a pointer to a composite **type literal** — `(*[]byte)(nil)`,
`(*map[string]int)(nil)`, `(*struct{ r int })(nil)` — reaches the same rendering by a different
route. `isTypeConversion` resolves a star target through its `types.Object`, which a type literal
does not have, so these shapes fell through to the regular call path and emitted a bare cast of
`default!` (`(ж<slice<byte>>)(default!)`) — a **null reference**, type erased. They are now claimed
as conversions, and the typed-nil interception renders the target through `convStarExpr` rather than
the resolved name, because an **anonymous** struct/interface element must be LIFTED to a named C#
type and `convStarExpr` is the site that performs that lift (the plain name path emits an
unresolvable raw `struct{…}` signature). `encoding/gob`'s `bootstrapType` table is the whole idiom
in one place, and it is a package-`init` NRE without this — every `reflect.TypeOf(…).Elem()` in it
saw a null descriptor:

```go
tBytes     = bootstrapType("bytes", (*[]byte)(nil))
tReserved7 = bootstrapType("_reserved1", (*struct{ r7 int })(nil))
```
```csharp
internal static typeId tBytes = bootstrapType("bytes"u8, ((ж<slice<byte>>)nil));
    [GoType("dyn")] partial struct Δtype {
        internal nint r7;
    }
internal static typeId tReserved7 = bootstrapType("_reserved1"u8, ((ж<Δtype>)nil));
```

Guarded by `TypedNilInterface` (extended with the slice/map/anonymous-struct type-literal targets),
`PointerToNilPointerIdentity`, and `NamedPointerReinterpret` (the canonical singleton keeps its
`object`-reference nil compare working); part of the reflection-bridge Phase-3 chip (see
`docs/phase4/DESIGN-reflection-bridge.md`).

### `reflect.Value.Interface()` is a boundary into interface space, so it packs the typed nil too

The rule above says the canonical instance is minted where the type becomes *observable* — at the
boundary into interface space — and pointer slots themselves keep plain `null`, because their
statically-typed world never needs the type carried. `reflect.Value.Interface()` is one of those
boundaries, and it is the one a *slot read* arrives at: the Value's data came out of a slice
element, an array element, a struct field, a map value or a `reflect.New(...).Elem()`, all of which
hold `null` for a nil `*T`. Handing that `null` straight out erases the type at the one call whose
entire job is to preserve it.

Go's own form makes the obligation explicit. `packEface` builds an interface from a **type** and a
**data word**, so a pointer-kinded Value with a nil data word packs as a non-nil interface holding
`(type=*T, value=nil)`. Managed storage has no data word to keep the type beside, so the bridge
reconstructs it from the Value's static type — which `makeTypedValue` recorded when the Value was
built — and re-encodes a null pointer-kinded read as that type's canonical typed nil:

```go
in := make([]*Int, 1)              // one zero-filled *Int element
v := reflect.ValueOf(in).Index(0)
i := v.Interface()                 // (*Int)(nil), NOT nil
data, err := i.(GobEncoder).GobEncode()   // assertion SUCCEEDS; nil receiver dispatches
```

The consumer that proves it is `encoding/gob`: `encodeGobEncoder` is literally
`v.Interface().(GobEncoder).GobEncode()`, and `big.Int.GobEncode` opens with `if x == nil` because
Go guarantees it will be reached that way. With the type erased, `i == nil` is true, `%T` prints
`<nil>`, the assertion takes its failure arm, and the nil-receiver method never runs — which is the
whole of math/big's `TestGobEncodingNilIntInSlice` / `TestGobEncodingNilRatInSlice`.

Two boundaries of the rule, both load-bearing:

- **Pointer kinds only.** An interface- or func-typed slot holding `null` *is* the nil interface /
  nil func, and Go packs that as the nil eface. Re-encoding those would invert the bug.
- **No new representation.** The value handed out is `ж<T>.NilBox` — the same singleton
  `reflect.Zero` of a pointer kind already yields (`GoReflect.ZeroValueOf`) and every emitted
  `nil`→`*T` conversion already mints. So the packed value compares equal to a language-level
  `(*T)(nil)` and asserts through the ordinary witness machinery. A slot whose static type resolves
  to no canonical nil keeps its `null`, so the rule can only *add* type information, never
  substitute a wrong one.

The fabrication path (`reflect.Zero`) and the write path were already on this encoding; this is the
**read** path joining them, so there is one nil encoding system-wide rather than two.

Guarded by `ReflectTypedNilInterface`, which runs typed nil → `Interface()` → `== nil` → type assert
→ nil-receiver dispatch across every slot kind that funnels through `makeTypedValue`, each paired
with a non-nil sibling so a blanket substitution fails as loudly as the erasure did, and pins
`Elem()` of a typed nil as still **invalid** (how a walker tells a typed nil from a pointer to a zero
value).

⚠ **The emission side is a separate, open question.** A typed nil crossing into an interface in
ordinary converted code — not through reflection — still collapses, because the pointer slot really
does hold `null` and the conversion site is not always able to see that it needs the box. Closing
that changes what `== nil` means for every converted interface and is a design decision, not a fix;
`encoding/gob`'s own `TestNilPointerInsideInterface` is its standing witness.

### A pointer crossing into an interface carries its static type, however the pointer was produced
The rule the boxing above is a special case of:

> A Go POINTER entering INTERFACE space is represented by its pointer BOX, carrying its static
> pointee type — however the pointer was produced.

Go's interface value is a (dynamic type, value) pair, so a pointer in one is never merely an
address: `any((*T)(nil))` is a **non-nil** interface whose `%T` prints `*T` and whose `x.(*T)`
assert succeeds with a nil result. Two managed renderings break that, and both silently:

- a bare **`null`** boxes as nothing at all — the type is gone, `x == nil` answers Go's `false`
  with `true`, the assert fails, and the reflection bridge finds no descriptor; and
- a **deref-aliased** pointer (`ref var p = ref Ꮡp.DerefOrNull()`) rendered by its value alias
  boxes a **copy of the pointee** — dynamic type `T` where Go says `*T`, pointer identity gone,
  and a nil one panics at the box instead of crossing intact.

A **non-empty** interface target already satisfies the rule and takes nothing from this: its
conversion goes through a generated adapter that holds the box and null-coalesces it to the same
canonical instance (`AdapterImplTemplate`). The **empty** interface (`any`) has no adapter — C#
boxes the value directly — so the treatment is emitted, as the golib `ж<T>` extension
`OrTypedNil()` (`box ?? ж<T>.NilBox`; `TypedNilBoxAccessor`):

```go
var ip *int
var st struct{ P *AErr }
sl := make([]*BErr, 1)
fmt.Printf("%T %T %T\n", ip, st.P, sl[0])   // *int *main.AErr *main.BErr
```
```csharp
ж<nint> ip = default!;
main_st st = default!;
var sl = new slice<ж<BErr>>(1);
fmt.Printf("%T %T %T\n"u8, ip.OrTypedNil(), st.P.OrTypedNil(), sl[0].OrTypedNil());
```

**Why the BOUNDARY and not the producers.** Emitting the canonical instance for a pointer's zero
value instead would be incomplete by construction: `var p *T` is an emission the converter owns,
but a struct's zero value, a `make([]*T, n)` element and a map miss are not — they are C# `default`,
which no emission intercepts. The boundary is finite and enumerable; the producers are not. It is
the same set of slots [an untyped constant boxed as `any`](#an-untyped-constant-boxed-as-any-boxes-at-gos-default-type)
already routes through, for the same reason (a value's Go dynamic type must survive being boxed):
call arguments including the variadic `...any` of the fmt family, composite-literal elements, keyed
**and positional** struct fields, map keys and values, `var` initializers, assignments, returns,
channel sends, and an explicit `any(p)` conversion. (The positional form is the one `encoding/gob`'s
`TestNilPointerPanics` table is written in — `[]struct{ value any; mustPanic bool }{{nilStringPtr,
true}, …}` — where every nil row has to arrive as a typed nil for gob to panic on it as Go does.)

Three BUILT-INs take an `any` slot without ever passing a declared parameter, so each applies the
boundary at its own emission arm rather than through the parameter loop: `panic`'s value (a
recovered typed nil must still answer `r.(*T)`), an `[]any` `append` element (the existing
element-to-`any` cast then wraps the result), and an `any`-keyed map's `delete` key — which has to
box to the same value the store did, or it matches no entry. None of the three has a corpus site
today (every `panic` argument in the standard library is an address-of composite, so the census is
zero), but they are the same boundary and the guard exercises all three.

The scope is a genuine `*T` (a `types.Pointer`). `unsafe.Pointer` is a Basic and renders as a
struct, and a NAMED pointer type renders as its generated wrapper struct — neither can be a null
reference, so neither has anything to carry. An address-of (`&x`), a `new(T)`, a `(*T)(nil)` and a
pointer CONVERSION of any of those (`(*Buffer)(&b)`, log/slog's buffer pool) render non-null by
construction and are left bare.

Corpus footprint: **465 sites across 145 files** (A/B of two seeded whole-stdlib reconverts), plus
one site where the deref-alias preamble became dead because the pointer now renders as its box —
`net/http`'s `http2h1ServerKeepAlivesDisabled(hs *Server)`, whose `var x any = hs` had been boxing a
`Server` VALUE and then interface-asserting it, so its `doKeepAlives` probe could never match the
pointer method set. Guarded by the extended `TypedNilInterface` (declared/field/element/map-miss
nils through every one of those slots, `%T`, `==`, and a type assert, output-compared vs `go run`;
before the fix it reports `dpi==nil true` where Go says `false` and panics dereferencing a nil box
on the return path).

### Pointer-to-interface assignment through selector fields
A selector assignment whose LHS field is an interface (`h.d = s`) uses the type of the **whole selector expression**, not just the selected identifier name, when deciding whether to wrap the RHS in an interface adapter. If the RHS is a pointer-typed identifier, the adapter receives the pointer box so a dereferenced value alias is not copied into a pointer-only implementation. The generated form matches other pointer-to-interface conversion sites:

```go
func assignDescriber(h *holder, s *Setting) {
    h.d = s
}
```
```csharp
internal static void assignDescriber(ж<holder> Ꮡh, ж<Setting> Ꮡs) {
    ref var h = ref Ꮡh.Value;
    ref var s = ref Ꮡs.Value;

    h.d = new SettingжDescriber(Ꮡs);
}
```

This is intentionally keyed on selector/index expression type instead of the root identifier, so struct fields such as `go/types`' `operand.expr ast.Expr` and ordinary behavioral fields both take the same path. Guarded by `PointerInterfaceStructField`, including the assignment case after the struct-literal cases.

## Empty Interface (`any`)
In Go, every type satisfies the method-less interface `interface{}`, now spelled `any`. This operates fundamentally like .NET's `System.Object`, so the converter maps the Go empty interface to `any` (a global alias for `object`). For example, a Go `func(i interface{})` becomes `void f(any i)`, and a `map[any]string` becomes `map<any, @string>`.

### A string literal in an `any` slot boxes through `@string` — as `(@string)"…"u8`
A Go string literal normally emits as a `"…"u8` `ReadOnlySpan<byte>` (which converts implicitly to `@string`). But a `ReadOnlySpan<byte>` has **no conversion to `object`**, so a string literal RETURNED (or returned as a tuple element) where the result type is the empty interface fails with CS0029 — testing's `func (f *chattyFlag) Get() any { return "test2json" }`. Such a result must box a golib `@string` (preserving Go string identity for a later `x.(string)` assertion), so `visitReturnStmt` renders the literal as `(@string)"…"u8` for an empty-interface result element:

```csharp
[GoRecv] internal static any Get(this ref chattyFlag f) {
    if (f.json) {
        return (@string)"test2json"u8;   // NOT a BARE "test2json"u8 (CS0029)
    }
    return f.on;
}
```

**The cast and the `u8` suffix are independent decisions.** The cast is what the slot requires — it turns
the span into an `@string`, which boxes with Go's `string` dynamic type. The suffix then just decides where
the literal's *bytes* come from: `u8` makes them a compile-time constant in the assembly's data section,
while a bare C# `"…"` is a UTF-16 constant that `Encoding.UTF8.GetBytes` has to transcode **on every
evaluation** of the site (measured 2.1–2.4× for ASCII, 4.2× for non-ASCII). The two were coupled for a
while — the emitter derived "no `u8`" from "needs the cast" — and every `any` position paid the transcode.
They are now separate flags (`castToGoString`, `u8StringOK`), and every `any` slot takes the combined form.

The coupling had a second, load-bearing consumer: `convBinaryExpr` suppressed `u8` inside a string
**concatenation** whenever the enclosing slot had `u8` off, because two u8 operands fold (C# folds UTF-8
literal constants) into a single `ReadOnlySpan<byte>` that then has no boxing conversion — `print("\n" +
"\t")` in runtime's `newstack` diagnostics is CS1503. Splitting the flags would have silently re-enabled
`u8` there, a **compile break that CNR cannot surface** (the emitted text is legal-looking; only the corpus
build fails). Concat suppression therefore has its own signal, `BasicLitContext.spanTargetUnsupported`,
which says "this slot cannot hold a bare span" and is set by every span-hostile site — `any` positions,
ValueTuple elements, attribute (struct-tag) arguments, `panic`'s object parameter, a deferred call's
generic type-parameter slot — independently of how a STANDALONE literal renders there. It propagates into
nested operands, so `"a" + "b" + c` stays suppressed all the way down.

`resultParamIsInterface` excludes the empty interface (`andNotEmptyInterface`), so the interface-conversion arm never fires for `any`; the per-element context sets `castToGoString` on (and leaves `u8StringOK` on) instead. Only string basic-literals consult those flags, so a non-string `any` result is unaffected. Also corrects a latent semantic bug in the multi-result form (`return "<no value>", true` from a `(any, bool)` result rendered a raw C# string, which would fail a Go `x.(string)` assertion). Guarded by `InterfaceCasting`.

The same boxing applies to an **assignment** whose target's static type is the empty interface — a plain
local (`arg = "<nil>"`, go/types format.go's sprintf over an `any` range variable, CS0029), a
selector/index target (`h.value = "field"`), and a mixed-statement reassignment all render the literal
`(@string)"…"u8`. `visitAssignStmt` threads the same `castToGoString`-on literal context
into each RHS conversion site when `lhsIsEmptyInterface` reports the target is `any` (the NON-empty
interface wrap stays with `convertExprToInterfaceType`, which the empty interface deliberately bypasses).
(Guarded by the `AnyStringLitAssign` behavioral test — an `any` local, an `any`-typed range variable, and
an `any` struct field each assigned a string literal, then type-switched on `string`, output-compared vs Go.)
The same boxing applies to every **composite-literal** position whose declared slot type is the empty
interface — the interface-wrap machinery deliberately bypasses `any` there too, so a string-literal
element otherwise renders either as the u8 span (no conversion to the generated `object` slot —
CS1503/CS0029) or as a bare C# string (compiles, but boxes a `System.String`, so a later Go
`x.(string)` assertion or `case string:` fails at runtime):

```go
type pair struct { label string; value any }
p  := pair{"tag", "val"}          // positional field
n  := &node{inner: "hi"}          // keyed field (typed, elided, and pointer-elided forms alike)
m  := map[string]any{"k": "mv"}   // map value
mk := map[any]int{"ky": 7}        // map key
s  := []any{"a", "b"}             // slice/array element
sp := [3]any{1: "sp"}             // sparse-array element
```

```csharp
var p  = new pair("tag"u8, (@string)"val"u8);        // NOT bare "val" (wrong boxed identity)
var n  = Ꮡ(new node(inner: (@string)"hi"u8));        // NOT a BARE "hi"u8 (CS1503)
var m  = new map<@string, any>{["k"u8] = (@string)"mv"u8};
var mk = new map<any, nint>{[(@string)"ky"u8] = 7};
var s  = new any[]{(@string)"a", (@string)"b"}.slice();
var sp = new array<any>(3){[1] = (@string)"sp"u8};
```

(`p`'s FIRST element is the plain `string` field — a positional struct element in a `string` slot now
renders `u8` too, matching what the keyed and elided forms already emitted; the slice/array `any`
ELEMENT form is the one position still on the bare-cast rendering.)

Keyed elements resolve their target slot in `convKeyValueExpr` (struct field via `info.Uses`; map/sparse
element and map key via the threaded composite type) and take the same `castToGoString`-on literal
context; positional struct fields and slice/array elements flip the per-element flags
(`useGoStringArg` on, `u8StringArgOK` left on) that `convExprList` feeds each element's literal context. A TYPE-PARAMETER slot is excluded even though its underlying constraint is an
interface (`isEmptyInterfaceTarget`) — a `~string`-constrained field takes the literal directly. Only
string basic-literals are affected; every non-`any` slot keeps its exact prior form. (Guarded by the
`AnyStringLitComposite` behavioral test — all the shapes above, each read back through a `string`
type-switch to prove runtime identity, output-compared vs Go.)

The same boxing applies to a **channel send** whose element type is the empty interface — both the
statement form and the select-case registration form. The send value previously converted with no
target-type context at all, so the literal's default `"…"u8` span failed against the channel's
`in object` send parameter (CS1503):

```go
ch := make(chan any, 1)
ch <- "text"                    // statement send
select { case ch <- "sel": … }  // select-case send (registration form)
```

```csharp
ch.ᐸꟷ((@string)"text");                              // NOT "text"u8 (CS1503)
switch (select(ch.ᐸꟷ((@string)"sel", ꓸꓸꓸ))) { … }    // registration form takes the same box
```

Both send positions route through a shared `convSendValueExpr` (`visitSendStmt.go`), which resolves
the channel's ELEMENT type and applies the same `isEmptyInterfaceTarget`/`isStringBasicLit` gate as
the assignment and composite-literal positions (a type-parameter element is excluded; only string
basic-literals are affected). The same helper also activates the NON-empty interface element wrap —
see [Maps and Channels](#maps-and-channels). (Guarded by the `AnyStringLitChanSend` behavioral test —
statement and select-case sends read back through a `string` type-switch and an `x.(string)`
assertion to prove runtime identity, output-compared vs Go.)

### An untyped constant boxed as `any` boxes at Go's DEFAULT TYPE

The numeric twin of the `@string` boxing above. Go materializes an untyped constant into an interface
at its **default type**: untyped int → `int` (go2cs `nint`, an `IntPtr`), untyped rune → `rune`
(`int32`), untyped float → `float64`. The boxed CLR type must match, because every observation of an
interface value dispatches on it: `x.(int)` (emitted `x._<nint>()`) panics on a boxed `Int32`
(`interface conversion: interface {} is int, not int` — both sides print "int", but one is `Int32`,
one is `nint`); a `case int:` type switch falls through; golib `AreEqual` bails early on
`leftType != right.GetType()`; and `fmt`'s `printArg` type-switch drops to its reflection fallback.
Two renderings need a cast, for different reasons:

- A bare int **literal** (or literal-only arithmetic) in the int32 range: `convBasicLit` renders it as
  a plain C# integer literal, which is `System.Int32`. (An int constant *outside* int32 range already
  renders `(nint)…L`, and a rune literal renders `(rune)'A'` / a float literal `2.5D` — all already the
  default CLR type, so those need nothing.)
- Any expression referencing a **named untyped constant** (`const fsize = 5`), which `visitValueSpec`
  emits as a golib `UntypedInt`/`UntypedFloat` **wrapper struct**, never a CLR number — `fsize + 1`
  evaluates through the wrapper's operator overloads and boxes the STRUCT, which matches *no* Go type
  at all. This holds at every magnitude and for every wrapped kind, which is why the int32-range test
  applies to the literal rendering only.

The cast applies at every empty-interface position — call argument (variadic `...any` included),
var-spec, assignment, `return`, channel send, slice/array element, keyed struct-field, map value, map
KEY (composite and index alike), and an explicit `any(...)` conversion:

```go
fmt.Sprintf("%s.v%d.%d", GOARCH, 8, i)   // variadic ...any argument
fmt.Sprintf("%s%c%03d", d, os.PathSeparator, seq)  // named untyped RUNE const under %c
v.Store(42)                  // non-variadic any argument (atomic.Value.Store)
var a any = 7                // var-spec
b = 8                        // reassignment
func r() any { return 42 }   // return
ch <- 3                      // channel send (chan any)
_ = []any{5}                 // slice/array element
_ = map[string]any{"k": 9}   // map value
_ = map[any]string{12: "x"}  // map key (and the matching m[12] lookup)
_ = holder{v: 3}             // keyed struct field
_ = any(7).(int)             // explicit conversion to any
```

```csharp
fmt.Sprintf("%s.v%d.%d"u8, GOARCH, (nint)(8), i);
fmt.Sprintf("%s%c%03d"u8, d, (int32)(os.PathSeparator), seq);
Ꮡv.Store((nint)(42));
any a = (nint)(7);
b = (nint)(8);
internal static any r() { return (nint)(42); }
ch.ᐸꟷ((nint)(3));
_ = new any[]{(nint)(5)}.slice();
_ = new map<@string, any>{["k"u8] = (nint)(9)};
_ = new map<any, @string>{[(nint)(12)] = "x"u8};   // and m[(nint)(12)]
_ = new holder(v: (nint)(3));
_ = ((any)(nint)(7))._<nint>();
```

`untypedConstBoxCast` (`convCallExpr.go`) drives the decision and returns the C# cast type (or none).
The constant's **kind** comes from `info.Types[arg]` — the type go/types has already DEFAULTED for the
interface slot — so a literal (`42`), a unary (`-5`), a binary (`1 + 2`), and a named untyped const are
all classified by one rule. Whether the rendering is a wrapper struct comes from
`exprRendersUntypedConstWrapper`, which walks the expression for an `*ast.Ident` resolving (via
`Info.Uses`) to a `*types.Const` whose **OWN** declared type is `UntypedInt`/`UntypedRune`/
`UntypedFloat` — `info.Types[arg]` cannot answer this, since it reports plain `int` for a literal and a
named untyped const alike. A defined-type-over-int constant (`type MyInt int`) is excluded (its box is
the `[GoType]` wrapper, asserted as `MyInt`). Call arguments reuse the per-argument `castArgToType`
plumbing; the other positions wrap through `boxUntypedConstAsDefaultType`.

**Deliberate exclusions and known residues:**

- A **type-parameter parameter** constrained by `any` (`func f[T any](v T)`) reads as an empty
  interface here too, but its instantiation binds the argument to the concrete `T` (int → the `nint`
  parameter), where a bare int literal already converts implicitly. Every gate uses
  `isEmptyInterfaceTarget` (which excludes type parameters), unlike the u8-span→`@string` case, where
  a `K=string` parameter genuinely needs the cast to bind.
- An untyped **COMPLEX** constant is out of scope: a named one renders as golib `GoBigConst` (a
  `BigInteger.Parse` of the literal text — `visitValueSpec`'s `writeUntypedConst` path, with its own
  standing TODO), a separate pre-existing gap that a `(complex128)` cast would not close. A complex
  *literal* renders `1D + 2D.i()` and already boxes as `complex128`.
- Untyped **bool** constants need nothing: `true`/`false` render as C# `bool`, already the Go type.
  Untyped **string** constants DO need the cast, but under the mirror-image rule — see below.
- A cast onto an expression that already renders at the default type — a **typed** constant
  (`const seqFirst int = iota` → `const nint`), or a local untyped const that `visitValueSpec`
  tightened to a concrete C# type (`const float64 derived = 7`) — is a harmless no-op the predicate
  does not attempt to suppress. It cannot know the declaration's chosen C# spelling from the argument
  alone, and an extra cast is cosmetic noise where a missing one is a runtime divergence.

**A variadic `...any` slot used to be carved out for literals**, on the theory that a boxed `Int32`
formats identically to `nint` under `%d`/`%v` so the cast was redundant noise on the most common call
pattern. That is wrong wherever the boxed value is **compared** rather than printed:
`encoding/base32`'s `testEqual("Read after EOF, n = %d, expected %d", n, 0)` boxed `n` as `nint` and `0`
as `Int32`, `AreEqual` compared the dynamic types first, and the assert fired with a message that reads
as an equality (`n = 0, expected 0`). It is also wrong for the `%!`-verb path, where the full-conversion
`fmt` names the argument via `reflect.TypeOf(arg).String()` and a boxed `Int32` reports `"int32"`
instead of Go's `"int"`. The carve-out is gone; the corpus-wide footprint of removing it is **two
lines in two files** (`go/token/position.cs`, `internal/buildcfg/cfg.cs` — Go's own stdlib almost never
passes a bare int literal into a `...any` slot), plus two `(int32)(os.PathSeparator)` lines in
`testing/testing.cs` from the rune-kind arm, which is a genuine `%c` fix for the Phase-4 test host.

**The `any` map KEY used to be excluded too**, because golib's `map` uses the default `Dictionary`
comparer (no numeric normalization — `nint(6) != Int32(6)`) and leaving *both* the composite key and a
literal index uncast kept `map[any]int{6:1}[6]` round-tripping. That self-consistency only held for
literal-vs-literal: a lookup by a real `int` VALUE (`m[n]`, necessarily boxed `nint` — the only form Go
can even distinguish) MISSED. `convIndexExpr` now applies the same cast to an untyped-constant index of
an `any`-keyed map, so store and lookup agree on `nint` and both forms hit — while `m[int32(6)]`
correctly misses, as Go requires.

(Guarded by the `UntypedIntInterfaceBox` behavioral test — each position read back through an
`x.(int)` assertion or an `int`/`int32` type switch, output-compared vs Go — and by
`AnyBoxedUntypedConst`, which pins the whole default-type class in one program: variadic and
non-variadic slots, named/literal/rune/float/beyond-int32 constants, `[]any`/`map[any]`/`map[K]any`/
struct-field/chan-send/return/explicit-conversion positions, the `map[any]` store↔lookup round-trip
plus its `int32` miss, and the dynamic types a type switch reports — output-compared vs `go run`. The
pre-fix converter diverges on 13 of its lines.)

**An untyped STRING constant takes the same treatment, under the MIRROR-IMAGE shape rule (2026-07-25).**
Go's default type for an untyped string constant is `string` — golib `@string` — and here it is the
LITERAL that boxes wrong: `convBasicLit` renders a string literal as a plain C# `"seed"`
(a `System.String`) or, where the position allows it, a `"seed"u8` `ReadOnlySpan<byte>` (a **ref
struct**, which cannot box at all — CS0029). A NAMED string constant needs nothing whether it is typed
or untyped, because it is emitted as an `@string` member — there is no `UntypedString` wrapper struct —
and its concatenations evaluate through `@string`'s own operators. So the string arm of
`untypedConstBoxCast` keys off the literal-only SHAPE (`constExprIsStringLiteralConcat`: string
`BasicLit`s joined by `+` through parens), exactly INVERTING the `exprRendersUntypedConstWrapper` test
the numeric arms apply.

The defect was that the coverage was **partial and therefore self-inconsistent**: the positions that
already boxed through `@string` did so via a `BasicLitContext` flag (`castToGoString`, set by
`anyBoxedStringLitContext` and its siblings, which also suppresses the `u8` form) — struct field, slice
element, map value, channel send, `return`, reassignment — while a **call argument**, a **var-spec**,
an `any`-keyed map **index lookup**, and an explicit `any("…")` conversion left the literal bare. So
`box{v: "seed"}` stored an `@string` while `eq(b.v, "seed")` passed a `System.String`, and Go's
`true`/`true` came back C# `false`/`false` — silently, with `%T` still printing `string` on both sides.
A literal CONCATENATION (`"se" + "ed"`) was uncovered at *every* position, because the flag only reaches
a `BasicLit`, and where the `u8` form survived (`new box(v: "se"u8 + "ed"u8)`) the emission did not even
compile.

Both mechanisms are kept, each doing what it is good at. The literal context still produces the tighter
`(@string)"seed"` at the positions that carry it, and the four missing positions were given it
(`convExprList` via a new `isStringBasicLit` branch beside `markAnyFieldLits`' identical either/or,
`visitValueSpec`'s `isAnyType` context, and `convIndexExpr`'s `any`-key context). `untypedConstBoxCast`
is the general net underneath — it catches the concatenations and any position that lacks the flag — and
all its application sites now route through `applyUntypedConstBoxCast`, which skips a rendering that
already leads with the cast so the two can never double up:

```go
box{v: "seed"}; eq(b.v, "seed")   // Go: true      var v any = "x"      m[any] lookup by "seed"
```
```csharp
new box(v: (@string)"seed");  eq(b.v, (@string)"seed");   // now true
any v = (@string)"x";         m[(@string)"seed"];
new any[]{(@string)("se" + "ed")};                        // the concat shape, previously bare
```

The cost is real and accepted, on the same reasoning that removed the variadic int carve-out: **every
string literal in a `...any` slot now carries the cast**, so `fmt.Println("x")` emits
`fmt.Println((@string)"x")`. That is 866 lines across 150 behavioral projects — uniform, mechanical, and
individually inspected. The alternative is a carve-out that leaves `x.(string)`, `case string:`, `==`,
and `%T` silently wrong on exactly the values a Go program is most likely to compare.

(Guarded by the `AnyBoxedUntypedConst` extension — literal, concatenation, named-untyped and named-typed
string constants at the variadic, non-variadic, var-spec, `[]any`, `map[any]` key store *and* lookup,
`map[K]any` value, keyed and positional struct-field, channel-send, `return`, explicit-conversion and
type-assertion positions, plus the dynamic type a `case string:` switch reports — output-compared vs
`go run`. The pre-fix converter leaves twelve of those renderings bare and emits four that do not compile
at all — CS0029/CS0030/CS1503 on the `"…"u8` span reaching an `object` slot.)

**The same cast applies to an interface `==`/`!=` comparison against an untyped `int` constant.** Go
compares an interface against a concrete value by its dynamic type *and* value, which the converter
lowers to golib's reflective `AreEqual` (`convBinaryExpr`'s interface-comparison branch — the
`iface == concrete` / `iface == iface` / `iface == ptr` cases). `AreEqual(object, object)` bails early
on `leftType != right.GetType()`, so a comparison operand's boxed *runtime* type must match, exactly as
a stored-then-asserted value's does. A bare C# int literal boxes as `System.Int32`, so `e.Value != 1`
against an `any` field holding a boxed Go `int` (`nint`/`IntPtr`) — container/list's
`TestIssue6349`, emitted `!AreEqual((~e).Value, 1)` — saw `IntPtr != Int32`, reported the values
UNEQUAL, and fired the test's error even though the value round-tripped as `1`. The interface-comparison
branch now casts the concrete constant operand to its default type (`!AreEqual((~e).Value, (nint)(1))`),
reusing the same `untypedConstBoxCast` predicate the boxing positions above key off — so the literal
boxes as Go's `int` dynamic type and the runtime-type guard passes. The cast is confined to the
`AreEqual` lowering (interface-vs-concrete / interface / pointer), so a bare int compared against a
*concrete* `int` — which lowers to C#'s native `==`, never `AreEqual` — stays bare (no noise). The
predicate yields nothing for the interface operand itself and for any non-constant operand, so only the
genuine boxed-constant-mismatch site is touched. (Guarded by the
`InterfaceUntypedIntCompare` behavioral test — an `any`-field-holding boxed int compared `==`/`!=`
against an int literal, negative-literal and literal-on-the-left forms, output-compared vs Go; the
pre-fix converter emits the bare literal and mis-reports every comparison unequal.)

## Multi-Assignment and Evaluation Order
All right-hand operands in assignment expressions in Go are evaluated before assignment to the left-hand operands. C# can operate equivalently using tuple deconstruction (_thanks to Eugene Bekker for the [suggestion](https://github.com/ritchiecarroll/go2cs/issues/6)_). For the following Go code:

```go
x, y = y, x+y
```
the equivalent C# code operates as follows:
```csharp
(x, y) = (y, x + y);
```

The simultaneous deconstruction is **mandatory** whenever the targets alias — a swap `s[i], s[j] = s[j], s[i]` shattered into `s[i] = s[j]; s[j] = s[i];` loses the first target's original value (the second read sees the already-overwritten slot). The converter routes a multi-target assignment to the deconstruction form when every target is a *reassignment to existing storage*, counted per element; an index, star-deref, or selector LHS is always such a write. This recognition keyed off `getIdentifier`, which resolves a target's root identifier by unwrapping index/star/selector/chan/array/map nodes but **not** `ParenExpr` — so an index whose base is a *parenthesized* pointer deref, `(*p)[i]` (the shape a pointer-receiver method uses to write its own named-slice element, e.g. a heap's `func (h *myHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] }`), resolved to a nil root and was **not** counted as a reassignment. The parallel assignment then fell through to sequential statements and the swap silently corrupted the slice — one element lost, the other duplicated:

```go
func (h *myHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] }
```
```csharp
// before: two sequential stores drop the temporary — (h)[i] and (h)[j] both end up as the old (h)[j]
[GoRecv] internal static void Swap(this ref myHeap h, nint i, nint j) {
    ((h)[i], (h)[j]) = ((h)[j], (h)[i]);   // simultaneous deconstruction, correct swap
}
```

Such a paren-deref index LHS is now counted as a reassignment directly (a single-element `(*h)[i] = v` write emits identically on either path, so nothing else drifts). The bug was invisible to compilation — the broken form compiled cleanly — and only surfaced when a converted test *ran*: it silently miscompiled `container/heap`'s test heap and the `internal/trace/internal/oldtrace` order heap (three swap sites). (Guarded by the `PointerReceiverSliceSwap` behavioral test — a pointer-receiver `swap` and a full slice reversal by repeated swaps, output-compared vs `go run`; the pre-fix converter loses elements and diverges. It is also what makes `container/heap`'s Go test suite validate — see [Phase 4](Roadmap.md#phase-4--convert-and-run-go-package-tests).)

The swap recognition above routes a *pure*-reassignment parallel assignment (every target already exists) to the deconstruction form. A **mixed** parallel `:=` — some targets reassigned, some newly declared, with `rhsLen == lhsLen` — was not covered: it satisfies neither `lhsLen == reassignedCount` nor the all-declared arm, and (unlike the call-deconstruction mixed cases below) has no single-call RHS to trigger `tupleResult`, so it fell through to **sequential** statements. When a reassigned target is *read by a later right-hand expression*, that read must see the target's ORIGINAL value (Go evaluates every RHS before any store); sequential emission reads the already-updated value. strconv's Ryū shortest-float rounding does exactly this — `dc, fracc := dc>>extra, dc&extraMask`, where `fracc` must read the pre-shift `dc`:

```go
dc, fracc := dc>>extra, dc&extraMask   // fracc must read the ORIGINAL dc
```
```csharp
(dc, var fracc) = (dc.Rsh(extra), (uint64)(dc & extraMask));   // whole tuple evaluated, then deconstructed
```

The converter now detects this read-after-write **hazard** (`lhsReusedInLaterRhs`: a written target's `types.Object` appears in a strictly *later* RHS element) and routes the mixed assignment through the same deconstruction path, where C# evaluates the entire right-hand tuple before deconstructing. It is scoped to the actual hazard, so a hazard-free mixed `:=` (`m, n := m+1, 100`) keeps its minimal sequential form. A newly-declared **int/uint** element takes its *explicit* type rather than `var` — `(a, b, nint c) = (b, a, a + b)` — because `var` would infer C# 32-bit `int` from a literal/int32 RHS instead of the Go-`int`-target `nint`; `string` (an `@string`'s u8 span cannot sit on a value-tuple LHS) and `unsafe.Pointer` hazards are excluded and keep the sequential form, a documented limitation with no stdlib occurrence. Like the swap bug this was invisible to compilation and surfaced only at runtime: it made strconv's shortest-float formatting round down, failing `math`'s `TestFloatMinMax` (`4e-324` vs Go's `5e-324`). (Guarded by the `ParallelAssignmentHazard` behavioral test — reassigned + newly-declared parallel forms whose later RHS re-reads a written target, output-compared vs `go run`; the pre-fix converter diverges. It is also what makes `math`'s Go test suite validate — see [Phase 4](Roadmap.md#phase-4--convert-and-run-go-package-tests).)

Go's **partial redeclaration** — `a, b := f()` where `a` already exists in the same scope — reuses `a` (assigns it) and declares only the new names. A blanket `var (a, b)` would re-declare the reused variable, so the converter emits `var` per *newly-declared* element only:

```go
frac, e := normalize(frac)   // frac is the existing parameter; e is new
```
```csharp
(frac, var e) = normalize(frac);
```

The same per-element mechanism handles a destructured element whose **address is taken** (`list, delta := netpoll(0); injectglist(&list)`). Such a local must be heap-boxed so its `Ꮡlist` companion exists, but the combined `var (list, delta) = …` deconstruction cannot declare it as a `ref var … = ref heap(…)`. The converter emits the escaping element's heap declaration first, then a mixed deconstruction-assignment in which the escaping element is the pre-declared box ref-local and the rest declare with `var`:

```go
list, delta := netpoll(0)
injectglist(&list)
```
```csharp
ref var list = ref heap<gList>(out var Ꮡlist);
(list, var delta) = netpoll(0);     // list is the box ref-local; delta is newly declared
injectglist(Ꮡlist);                 // Ꮡlist now exists
```

Without this, `&list` emits `Ꮡlist` with no box (CS0103), and the `Ꮡ(value)` copy fallback would silently lose writes made through the pointer. (Guarded by the `TupleDestructureEscapingLocal` behavioral test — a mutate-through-pointer proves the real local is updated; runtime exercises it in the `netpoll` poll loops.)

A subtler case: a newly-declared tuple element can be flagged *escaping* by analysis yet need **no** heap box — typically an already-pointer local that is merely returned (`pp, now := pidleget(now)`, where `pp` is a `*p` that the function returns). The heap-decl path above only owns elements that produce an actual `ref var … = ref heap(…)`; an escaping element with no such declaration must still be counted as newly-declared so it receives its `var`, or the deconstruction emits `(pp, now) = …` with `pp` declared nowhere (CS0103). Both the mixed (`(var pp, now) = …`, reusing the value parameter `now`) and the all-shadowing (`var (ppΔ1, gpΔ1) = …`) forms are handled. (Guarded by the `TupleMixedDeclareReassign` behavioral test; runtime hits it in `pidlegetSpinning` and `findRunnable`.)

**A tuple deconstruction into INTERFACE variables hoists the call when a component needs converting.** Reassigning a multi-value call into pre-declared interface locals (`c, err = sd.dialTCP(…)` with `var c Conn`) can require a per-component interface conversion C#'s tuple assignment cannot perform implicitly — a `ж<TCPConn>` component satisfies `Conn` only through its generated pointer adapter, an *explicit* conversion (CS0266 ×11 in net's dial.go). Mirroring the return-statement tuple arm, the call is hoisted into temp markers and each component converts in a tuple literal:

```csharp
var (ᴛ1, ᴛ2) = Ꮡsd.dialTCP(ctx, laΔ1, raΔ1);
(c, err) = (new TCPConnжConn(ᴛ1), ᴛ2);
```

The arm fires only for a statement-position deconstruction (one call RHS, several LHS) where some non-empty-interface target's tuple component is a non-identical, non-interface type; all other deconstructions keep the direct form. (Guarded by the `InterfaceCasting` extension `makeCounter` — a `(*Counter, error)` call deconstructed into an `Incrementer` — runtime-verified against Go.)

### A SELECTOR left-hand side counts as a reassignment — a field swap must stay simultaneous

The paren-deref fix above closed the *index* form of the target-classification gap; the **selector** form (`x.f, y.g = …`) had the same hole. The classifier deliberately drops a selector LHS's root identifier (`getIdentifier` would return the *base* — a package name, or a struct local — which, not itself being reassigned, would wrongly be counted as a new declaration and take a `var` prefix), and then counted it as **neither** reassigned nor declared. A parallel assignment whose targets are *all* selectors therefore satisfied no tuple-path gate — not `lhsLen == reassignedCount`, not `lhsLen == declaredCount`, and (with no single-call RHS) not `tupleResult` — and shattered into sequential stores, losing the swap's implicit temporary:

```go
// regexp/onepass.go — makeOnePass: put the empty-match leg in inst.Out
inst.Out, inst.Arg = inst.Arg, inst.Out
```
```csharp
// before — both fields end up holding the ORIGINAL Arg
inst.Value.Out = inst.Value.Arg;
inst.Value.Arg = inst.Value.Out;

// after — the simultaneous deconstruction C# gives for free
(inst.Value.Out, inst.Value.Arg) = (inst.Value.Arg, inst.Value.Out);
```

Note the tell in the "before": the very next statement of the same Go function swaps two plain *locals* (`matchOut, matchArg = matchArg, matchOut`) and was already emitted correctly as `(matchOut, matchArg) = (matchArg, matchOut);` — the divergence was purely the target *shape*. The consequence was silent: every `InstAlt` whose empty-match leg needed swapping got a corrupted dispatch, so `regexp` quietly lost its one-pass engine for `^[a-c]*$`, `^(?:a*)$`, `^.bc(d|e)*$` and friends, and `^[a-c]+$` stopped matching `"abc"` at all. A field write is a write to existing storage exactly as the index and star-deref forms are, so it is now counted as reassigned like them — which also aligns single-selector assignments (`h.flags &= ^writing`) with the narrowing-cast rendering a plain-ident target already got. (Guarded by the `ParallelAssignmentHazard` extension — a pointer-receiver field swap, a cross-struct rotate reading pre-assignment values, and a package-var swap; and by the `RangeVarReassign` / `AndNotAssignNarrow` goldens, whose re-baselines are this routing change.)

### An address-taken reference-typed local heap-boxes too — `Ꮡ(value)` copies are only for reads
An INHERENTLY heap-allocated local (interface/pointer/slice/map/chan/func) is already a
reference, so escape analysis blanket-marks it and the box machinery historically skipped it —
`&local` fell back to the `Ꮡ(value)` **copy** constructor. That is only sound when nothing
writes through the pointer: dwarf's `zeroArray(&typ)` (with `typ Type`, an interface local)
writes `*t = &tt` in the callee, and the copy-box silently dropped the write (C# printed the
un-replaced value — a behavioral divergence, not a compile error). The box predicate
(`identHasHeapBox`) now boxes such a local when its address is **genuinely taken** — by a
capturing closure (the pre-existing box-ref-var case) or anywhere in the current function
(memoized `&ident` scan) — so `&swapped` references a real aliasing box:

```go
var swapped Animal = Dog{}
replaceAnimal(&swapped)      // callee: *a = &Cat{}
```
```csharp
ref var swapped = ref heap<Animal>(out var Ꮡswapped);
swapped = new Dog(nil);
replaceAnimal(Ꮡswapped);     // callee writes through the SAME box — "Meow!"
```

Details: the box declaration always uses the parameterless `heap<T>(out …)` form for these
(`new Animal()` on an interface is CS0144, and the reference-like zero value is exactly what
the box provides); a `[]T` slice local routes to `heap<slice<T>>` (the array-branch prefix
test mistook `[]` for an array and emitted a mismatching `heap<array<T>>`); and the
pointer-form ident render in convIdent deliberately keeps the PLAIN value render for these
locals (`new Middle(Inner: inner)` wants the held pointer; only an explicit `&inner` wants
the `ж<ж<T>>` box, via convUnaryExpr). Non-escaping and never-addressed reference locals are
unchanged (no churn). (Guarded by `InterfaceCasting`'s `replaceAnimal` — the swap is visible
through the original variable; the churned goldens `PointerToPointer`,
`UnsafePointerReinterpret`, `DerefPointerToField`, `PointerCastSliceRange`,
`EscapedLoopVarSiblingIndex` all re-verified against Go.)

### An address-taken NAMED RESULT heap-boxes too
A named result is declared in the function signature, not by any body statement, so the escape
analysis' define-walk never reached it — it was analyzed only when it also happened to sit on a
`:=` LHS somewhere. A named result whose address is taken but which is never `:=`-reassigned
(text/tabwriter's `func (b *Writer) flush() (err error) { defer b.handlePanic(&err, "Flush"); … }`,
where the deferred handler writes `*err = nerr.err`) was therefore left unboxed: `&err` fell back to
the `Ꮡ(err)` **copy** box, so the handler wrote a copy while `return err` read the original —
silently dropping the error (Go promotes an address-taken named result to the heap). The escape
analysis now walks every named result and marks it escaping when its address is genuinely taken
(`&err`, `&err.field`, `&err[i]`), so the existing heap-box machinery boxes it at entry — the box
`Ꮡerr`, the deferred handler's write through the pointer, and the final `return err` all reference
one slot:
```go
func (b *Writer) flush() (err error) { defer b.handlePanic(&err, "Flush"); … }   // handlePanic: *err = e
```
```csharp
internal static error /*err*/ flush(this ж<Writer> Ꮡb) {
    heap<error>(out var Ꮡerr);                            // box declared before the try
    GoFrame ᒐ = default;
    try {
        ref var b = ref Ꮡb.DerefOrNull();

        ref var err = ref Ꮡerr.ValueSlot;                 // value alias inside
        defer(Ꮡb.handlePanic, Ꮡerr, flushˢ, ref ᒐ);       // the BOX is passed, not Ꮡ(copy)
        b.flushNoDefers();
        err = default!;
    }
    catch (Exception ᒐex) when (GoFrame.IsPanic(ᒐex, out PanicException? ᒐp)) { GoFrame.Capture(ᒐp); }
    finally { ᒐ.Run(); }
    return Ꮡerr.ValueSlot;                                // reads the SAME slot the handler wrote
}
```
The trigger is address-taken **specifically** — a named result merely referenced or written inside a
closure is not boxed (a C# closure already captures the outer local by reference), so a common
`defer func(){ err = wrap(err) }()` result keeps its plain declaration (no churn). The verdict is
only ever SET true, so a result whose address is never taken is byte-unchanged. Function literals
take the same treatment. (Guarded by `NamedResultAddressEscape` — an error result written through
`&err` by a deferred handler and a value `int` result mutated through `&n`, output-compared vs Go;
`PointerToInterfaceParamDeref` re-baselined to the box form, its output unchanged since its handler
only *reads* `*err`.)

### An address-taken VALUE PARAMETER heap-boxes too
A value parameter, like a named result, is declared in the **signature** — not by any body statement —
so the escape analysis' define-walk never reached it either. Parameters were additionally kept out of
the full escape analysis on purpose, which left exactly **one** parameter trigger in the pass: the
capture-mode-method check (`markCaptureModeBoxedParams`, *A capture-mode method called on a value
PARAMETER* below). A plain `&param` was not a trigger, so `&r` fell back to the call-site `Ꮡ(r)`
**copy** box — it compiles, and silently drops every write the callee makes through the pointer:

```go
func DrawMask(dst Image, r image.Rectangle, src Image, sp image.Point, …) {   // image/draw
	clip(dst, &r, src, &sp, mask, &mp)   // clip narrows r (and sp/mp) IN PLACE
	if r.Empty() { return }              // …but the narrowed r was never seen
```

The escape pass now marks a value parameter whose address is genuinely taken — `&r`, `&r.field…`
(a value-field chain rooted at the parameter), or `&r[i]` — via **`objectAddressTaken`**, the same
generic per-object scan the named-result arm above uses (renamed from `namedResultAddressTaken` now
that it serves both signature-declared categories). The existing entry-time box machinery then boxes
the parameter at entry, exactly as the capture-mode trigger does:

```go
func clipParam(r Rect) Rect { clip(&r, 5, 5); return r }
```
```csharp
internal static Rect clipParam(Rect rʗp) {
    ref var r = ref heap(rʗp, out var Ꮡr);   // ENTRY-time box, never a call-site copy
    clip(Ꮡr, 5, 5);                          // the callee writes THIS storage…
    return r;                                // …and the body reads it back
}
```

Entry-time boxing is what preserves Go's semantics on both sides: the callee's writes are visible to
the rest of the body, and the **caller's** argument stays untouched (the parameter is still by-value —
the box is initialized from the incoming `ʗp` copy). The `&param.field` form renders through the box
accessor (`Ꮡb.of(Box.ᏑR).of(Rect.ᏑMin)`) and `&param[i]` through `Ꮡa.at<E>(i)` — the latter now
superseding, at its parameter sites, the array-parameter fallback that copy-boxed the `array<T>`
*wrapper* (`Ꮡ(value).at<byte>(0)`, kept for any array base that still owns no box) and was correct
only because the wrapper shares its `T[]`. An ARRAY param folds its Go by-value clone into the box
init: `ref var a = ref heap(aʗp.Clone(), out var Ꮡa);`.

**An INHERENTLY-HEAP parameter takes only the BARE `&p` form** (`paramAddressTakenNeedsBox`). A
slice/map/chan/interface/func — and a type parameter, whose underlying is its constraint interface —
is already a reference, so only the address of the reference *variable itself* needs a box; `&p[i]`
addresses the **shared backing array**, which the emitted element form `Ꮡ(p, i)` already aliases
correctly, and Go likewise does not heap-promote a slice header for an element address. This mirrors
`identHasHeapBox`'s own box gate, which is what makes the restriction load-bearing rather than an
optimization: marking a verdict that gate then refuses would leave `identEscapesHeap` set with **no**
box, and that map is read raw by the capture analysis and several emitters. Measured on the first cut
(which did not restrict), 48 of 149 newly-boxed parameters were `&s[i]`-only slices — including
`unicode.is16`/`is32`, `slices.Equal`/`Index`, `subtle.XORBytes`, `crypto/internal/alias`, and the
Windows syscall buffer paths — every one allocating a box per call for no semantic gain.

**The analysis trigger and the emission gate must move together.** `markCaptureModeBoxedParams`
records the reason and `paramBoxReasonHolds` (read by visitFuncDecl's parameter preamble, its
`processPotentialCapture` box-ref arm, and convFuncLit's literal prologue) re-verifies it against the
declaring ident; a reason recorded by analysis but missing from the gate leaves body uses referencing
a box that was never declared (CS0103), and the reverse declares a box nothing references. Both gained
the address-taken trigger in the same change. The gate stays *narrow* in the other direction: a param
that leaks into `identEscapesHeap` some other way — a mixed `data, pc, line := …` define re-uses the
param object, so the define walker escape-analyzes it (debug/gosym's `slice`) — still keeps its
historical unboxed emission. Function-literal params take the identical treatment
(`funcLitHeapBoxParamIdents`), and a boxed param referenced from a nested closure is box-ref'd rather
than snapshot-copied (see *CaptureModeParamClosure* below), so the closure, the body, and the callee
all share the one parameter variable Go gives them.

This was the **fifth** path in the `Ꮡ(value)` copy-box family, after the pointer-to-array element,
the slice/array field of a receiver, the field-addressed value local, and the named result — and the
**value RECEIVER**, below, is the sixth and last. The corpus consumers the parameter arm corrects are
all silent-wrong-answer bugs, not compile errors:

| Package | Site | Before → after |
|---|---|---|
| `crypto/x509` `parser.cs` | `parseValidity(der cryptobyte.String)` calls `parseTime(&der)` **twice** | two independent `Ꮡ(der)` copies → one `Ꮡder`: the DER cursor now advances, so `notAfter` is parsed from the bytes after `notBefore` instead of re-parsing the same ones. `parseName`, `forEachSAN`, `parseBasicConstraintsExtension`, `parseExtKeyUsageExtension` and `parseCertificatePoliciesExtension` have the same non-advancing-cursor shape (`der.ReadASN1(&der, …)`). |
| `crypto/x509` `verify.cs` | `Verify(opts VerifyOptions)` → `systemVerify(&opts)`, `isValid(…, &opts)`, `buildChains(…, &opts)` | three separate copies → one shared `Ꮡopts`. |
| `net/http` `server.cs` | `Serve(l net.Listener)` registers `trackListener(&l, true)` and defers `trackListener(&l, false)` | the register and deregister boxed **different** copies, so the deregister's `delete(srv.listeners, ln)` could never match the registered key — every served listener leaked. Now both pass `Ꮡl`. |
| `database/sql` `sql.cs` | `(*Rows).close(err error)` → `fn(rs, &err)` plus a `withLock` closure that assigns `err` | the hook's `*err = …` wrote a copy while `return err` read the original. Now the closure (`Ꮡerr.ValueSlot = …`), the hook, and the return share one slot. |
| `testing/slogtest` `slogtest.cs` | `wrapper.Handle(…, r slog.Record)` → `h.mod(&r)` then `h.Handler.Handle(ctx, r)` | `mod` mutated a copy, so the wrapped handler received the **unmodified** record — the whole wrapper mechanism was a no-op. |

(Guarded by the `AddressOfParamWrite` behavioral test — a value parameter clipped in place through
`&r` by a callee that writes, a `&param.field` bump, an `&param[i]` bump on an array parameter, and
three controls that must not change: a read-only `&param`, an address-taken *local*, and a parameter
whose address is never taken; the caller's own argument is printed after the call to prove it stayed
untouched. Output-compared vs `go run` — under the pre-fix emission the three write cases all printed
the unmodified input.)

**The value RECEIVER is the same category — and it closes the family.** A method's receiver is the
*third* thing declared in a signature rather than by a body statement, so it failed for exactly the
reason the named result and the value parameter did: it arrives on `funcDecl.Recv`, which neither the
define-walk nor `markCaptureModeBoxedParams` (which walks `funcType.Params`) ever reaches. Two
distinct symptoms, both closed by **`markAddressTakenBoxedReceiver`**:

| Go (receiver starts `Box{Rect{0,16}, 6}` / `Trio{7,8,9}`) | Pre-fix C# | Go says | Pre-fix C# said |
|---|---|---|---|
| `func (b Box) Bumped() int { bumpBox(&b); return b.Tag }` | `bumpBox(Ꮡ(b));` | `16` | `6` |
| `func (b Box) Clipped() … { clip(&b.R, 5, 5) … }` | `clip(Ꮡ(b).of(Box.ᏑR), 5, 5);` | `5 5` | `0 16` |
| `func (a Trio) Elem() … { bump(&a[1]) … }` (array receiver) | `bump(Ꮡa.at<nint>(1));` | `7 18 9` | **CS0103** — `Ꮡa` never declared |

The array-receiver row is not a silent wrong answer but a **hard compile error**: `convUnaryExpr`'s
array-base copy-box fallback (`Ꮡ(value).at<E>(i)`, kept for an array base that owns no box) is keyed on
`identIsParameter`, and the receiver is deliberately *not* a parameter in that model — so the naive
identity-box form was emitted for a box nothing declared. Giving the receiver a real box fixes both
symptoms with one mechanism.

The convention is the parameter's, reused verbatim rather than invented: the incoming value takes the
`ʗp` name and the entry preamble re-declares the Go name as the boxed ref alias, with an ARRAY
receiver folding its Go by-value clone into the box init exactly as an array parameter does.

```go
func (b Box) Bumped() int { bumpBox(&b); return b.Tag }
func (a Trio) Elem() int  { bump(&a[1]); return a[1] }
```
```csharp
public static nint Bumped(this Box bʗp) {
    ref var b = ref heap(bʗp, out var Ꮡb);
    bumpBox(Ꮡb);
    return b.Tag;
}
public static nint Elem(this Trio aʗp) {
    ref var a = ref heap(aʗp.Clone(), out var Ꮡa);   // the by-value clone folds into the box init
    bump(Ꮡa.at<nint>(1));
    return a[1];
}
```

**The public surface is unchanged**, which is what makes the rename safe: only the receiver's *name*
moves, never its C# type, so the method stays the value-receiver extension Go's method set requires —
still callable on a value, still callable through a pointer (which copies into the receiver, so the
caller's own variable is untouched, matching Go), and still satisfying an interface it implements by
value. `RecvGenerator` is unaffected because it is gated on `IsRefRecv` (`this ref T`), and a value
receiver never carries `ref`; `[GoRecv]` is likewise emitted only for a `this ref ` signature. The box
is an implementation detail of the body, exactly as the parameter `ʗp` + heap preamble is.

Analysis and emission move together here too — `markAddressTakenBoxedReceiver` records and
**`recvBoxReasonHolds`** (read by `paramNeedsHeapBox`, which now consults `funcDecl.Recv` before the
params walk) re-verifies. The receiver's reason set is deliberately **narrower** than a parameter's:
just the address-taken predicate. A capture-mode (direct-ж) receiver is already served by
`packageDirectBoxReceiverMethods`, which emits the box *as* the receiver instead of renaming it, and a
receiver the capture analysis routed to box-ref storage must never take the `ʗp` form at all — so
neither `bodyCallsCaptureModeMethodOn` nor `isLambdaBoxRefVar` joins the receiver gate.

The receiver reuses `paramAddressTakenNeedsBox`, so an inherently-heap receiver still boxes only for
the bare `&r`. **Measured, that restriction rejects zero corpus sites today** — unlike the parameter
arm's 48 of 149 — and the reason is worth recording: the parameter over-boxings came from the first
cut *also* recording `packageCaptureModeBoxIdents`, which forces `identHasHeapBox` to grant a box. The
receiver arm never records it, so for a `&r[i]`-only slice receiver `identHasHeapBox`'s own gate
refuses the box independently and the emission is byte-identical either way. The restriction is kept
because it keeps the analysis verdict and that gate in **agreement**: marking a verdict the gate then
refuses leaves `identEscapesHeap` set with no box, and that map is read raw by the capture analysis and
several emitters.

Corpus footprint of the fix, from a two-seeded-root A/B (master converter vs fixed, both reconverting
all 305 projects into their own temp root): **3 receiver sites across 2 files** — `encoding/base64`'s
`WithPadding` and `Strict` and `encoding/base32`'s `WithPadding`, each `func (enc Encoding) … *Encoding`
returning `&enc`. All three are *correct-by-luck* under the old emission: `&enc` is the last operation,
after every mutation, so the `Ꮡ(enc)` copy carried the mutated value out. They are now one storage
identity rather than two, at the same single allocation. That there is no live victim is the point —
this path was closed at its root rather than after a sixth package was found broken by it.

**The receiver decision is MODE-STABLE, structurally** — `-stdlib` and `-tests` cannot disagree about
it, and that is worth stating because the sibling category *can*. A package-level var's address may be
taken by the package's own `_test.go`, which a production `go/packages` load never sees, so the storage
shape has to be reconciled deliberately (*A global addressed only by the package's own `_test.go` is
still heap-boxed*, below). A **receiver is function-scoped**: its address can only be taken inside its
own method body, and a production method's body is production source that no `_test.go` can add a
statement to. `markAddressTakenBoxedReceiver` reads `funcDecl.Recv` against `funcDecl.Body` and nothing
else, so admitting test files to the analysis universe cannot change its answer. Measured against the
claim rather than assumed: a whole-stdlib `-stdlib` reconvert and the `-tests` pipeline's regenerated
production `.cs` for `encoding/base32` and `encoding/base64` are byte-identical. (A board row read the
opposite from a `go2cs.exe` built *before* this fix, and filed the resulting sweep drift as an open
mode-instability that must never be banked; the retraction — and the bank — are in
[`phase4/BOARD-next-validation-candidates.md`](phase4/BOARD-next-validation-candidates.md).)

(Guarded by the same `AddressOfParamWrite` behavioral test, extended: a value receiver bumped in place
through `&b`, a `&recv.field` clip, a `&recv[i]` bump on an ARRAY receiver, and four controls that must
not change — a `&recv[i]` on an inherently-heap **slice** receiver, a read-only `&recv`, a pointer
receiver, and a receiver whose address is never taken — plus the two public-surface cases, the boxed
value-receiver method called through a pointer and through an interface. Output-compared vs `go run`.)

### A field-addressed value local heap-boxes — `Ꮡ(x).of(…)` copy-boxes orphan writes
Escape analysis's address-of walk marked `&x` (direct) and `&x[k]` (element) but had **no
selector arm**, so a value-struct local whose FIELD address was taken in plain assignment
(or composite-literal / return) position stayed unboxed, and convUnaryExpr fell back to the
`Ꮡ(x).of(T.Ꮡval)` **copy**-box — writes through the pointer landed in the copy and were
silently lost (Go reads the write back through `x`; C# printed the original value — a
behavioral divergence, not a compile error). The walk now peels a value-field selector
chain (`x.f1.…fn`, every hop a direct `FieldVal` selection with no pointer indirection) to
its root ident and marks the root escaping, so the emission routes through the identity box:

```go
x := Thing{val: 7}
p := &x.val
*p = 99
return x.val                       // Go: 99
```
```csharp
ref var x = ref heap<Thing>(out var Ꮡx);
x = new Thing(val: 7);
var p = Ꮡx.of(Thing.Ꮡval);
p.Value = 99;
return x.val;                      // 99 — the pointer aliases x's box
```

Multi-hop chains chain the accessors (`&w.inner.val` → `Ꮡw.of(Wrap.Ꮡinner).of(Thing.Ꮡval)`),
and a field promoted through a VALUE embed roots at the local too (`&o.ev` →
`Ꮡo.of(Outer.Ꮡev)`). A hop that crosses a POINTER — an explicit `w.ptr.val` deref or a field
promoted through an embedded pointer (both are `Selection.Indirect()`) — aliases the
POINTEE's storage instead, so the root deliberately stays unboxed: `w.ptr.of(Thing.Ꮡval)`
already writes through the held box. (Guarded by `LocalStructFieldAddr` — plain, nested,
method-body, value-embed-promoted, composite-literal, and return positions plus the
pointer-hop negative control, all output-compared vs Go; the one churned golden
`UnsafePointerParamPin` — `&h.v` under `unsafe.Pointer` — re-verified.)

### A PACKAGE-LEVEL function literal's own locals are analyzed too
Every heap-box rule above is decided by the escape-analysis pass, and that pass reached a variable
only through its declaring **function declaration**: the driver walked `*ast.FuncDecl` bodies and ran
the define-walk (`:=`, `var`, range/for/if/switch/type-switch init defines) against each body. A
function literal that is not inside any declaration — a **package-level `var` initializer** — hit a
separate arm that marked its parameters and named results but never ran that walk, so **none of its
own locals were ever analyzed** and every one of them stayed unboxed, whatever the body did with it.

That is the shape of every Go test table (`var tests = []struct{ name string; f func() }{{"…",
func(){ … }}}`) and of the `sync.OnceFunc`/`OnceValue` package-level initializers, and it produced
both failure modes at once:

```go
var InitWSA = sync.OnceFunc(func() {          // internal/poll fd_windows.go
    var d syscall.WSAData
    e := syscall.WSAStartup(uint32(0x202), &d)   // fills d
    …
})
```
```csharp
// before — Ꮡ(d) COPY-boxes: WSAStartup filled a copy and the Winsock data was lost (silent)
Δsyscall.WSAData d = new();
var e = Δsyscall.WSAStartup((uint32)0x202, Ꮡ(d));
// after — the identity box, exactly as an in-declaration local has always emitted
ref var d = ref heap(new Δsyscall.WSAData(), out var Ꮡd);
var e = Δsyscall.WSAStartup((uint32)0x202, Ꮡd);
```

The compile-visible half is a capture-mode receiver: sync `mutex_test.go`'s `misuseTests` table does
`var mu sync.Mutex; mu.Unlock()` inside such a literal, and an unboxed `mu` emits the VALUE receiver
form, which binds no `ж<Mutex>` extension overload at all (CS1929 ×16). The define-walk is now a
shared helper both arms call, so a package-level literal gets byte-identical treatment to a
declaration body. Literals **nested inside** a declaration were already walked against the enclosing
body (a superset), and the analysis short-circuits per object, so the overlap is a no-op — the
behavioral corpus is byte-identical and the full-stdlib footprint is exactly the three package-level
initializers that needed it (`internal/poll`, `internal/syscall/windows`, `internal/sysinfo`).
(Guarded by `PkgLevelFuncLitLocals` — every declaration form inside a package-level literal (`var`,
`:=`, for/if/switch init, range value, a nested closure) plus a standalone package-level literal and
an in-declaration control, output-compared vs Go.)

### A pointer-receiver METHOD VALUE heap-boxes its receiver — the implicit `(&x).M`
Go's spec makes `c.split` shorthand for `(&c).split` when `c` is addressable and `split` has a
pointer receiver: the method **value** binds a pointer into `c`'s own storage, so every write it
makes through its receiver is visible in `c` afterwards. That is the same escape condition as an
explicit `&c` — just written without the `&`, which is precisely why the address-of walk above could
not see it. The local stayed unpromoted and emission fell back to the copy box `Ꮡ(c).split`: it
compiled and ran, but the method mutated a **copy** and the caller's writes were silently dropped.
bufio's `s.Split(c.split)` is the real site — the scan counter never decremented, so the reader
"stopped with 10000 left to process".

Escape analysis now recognizes a method value that selects a pointer-receiver method on the local's
own storage — the bare ident, or a non-indirect value-field chain rooted at it, reusing the same
root walk the explicit-`&` arm uses — and marks it escaping, so the emission becomes the aliasing
box:

```go
c := counter{n: 100}
sum := applyInt(c.dec, 5, 7)       // (&c).dec — Go: c.n is 88 afterwards
```
```csharp
ref var c = ref heap<counter>(out var Ꮡc);
c = new counter(n: 100);
nint sum = applyInt(Ꮡc.dec, 5, 7);   // Ꮡc aliases c — was Ꮡ(c), a copy
```

The rule is position-general (argument, assignment, composite element, return) and covers value
**parameters** and named **results** as well as locals: a parameter takes the entry-time box
(`ref var c = ref heap(cʗp, out var Ꮡc);`) rather than a call-site copy, exactly as the capture-mode
call form already did — the three emitters that materialize a parameter box now share one predicate
(`paramBoxReasonHolds`), so an analysis reason can no longer be recorded without its box being
declared (CS0103). An **inherently-heap** named slice/map/chan receiver takes the box too (`Ꮡ(l)`
would clone the slice header, orphaning `*l = append(*l, v)`).

Three boundaries stay deliberately outside the rule. A direct **call** `c.dec()` is not a method
value: it binds C#'s `this ref counter c` extension receiver against the variable and is already
correct, and promoting for it would heap-box every local that calls a pointer-receiver method. A
**pointer-typed base** (`p.M` where `p` is `*counter`) passes the pointer value and takes no address
of `p`. A **value-receiver** method value (`c.peek`) copies the receiver at evaluation time in Go —
which is what the existing lambda-snapshot emission (`var cʗ1 = c; … cʗ1.peek()`) already does.
(Guarded by `MethodValueReceiverEscape` — local, value parameter, named result, value-field chain,
named-slice, and closure-formed method values, plus all three negative controls, output-compared vs
Go. Note the still-open residue: a method value bound to a *variable* — `f := c.dec` — takes the
lambda-snapshot path, which copies the receiver even when the local is promoted.)

A **blank-identifier element** in a split multi-assign is a C# discard, never a declaration. Go's `_, _, _, _ = a, b, c, d` (a common "mark these used" idiom) is emitted as one bare discard per element with **no** `var` — the per-element discard test keys off each LHS ident, not just the single-LHS case, so every blank stays a discard:

```go
_, _, _, _ = fi, fn, gi, gn
```
```csharp
_ = fi;
_ = fn;
_ = gi;
_ = gn;
```
A blanket `var _` on each would declare `_` once and then collide on every later element (CS0128 "a local named `_` is already defined"). (Guarded by the `BlankIdentifierCollision` behavioral test; runtime hits it in `softfloat64`'s `fdiv64`.)

## Short Variable Redeclaration (Shadowing)

When using Go's short variable declaration syntax, e.g., `x := 2`, a variable can be redeclared in a lesser (nested) scope. The inner declaration "shadows" the outer one: the inner instance is manipulated while the outer value is preserved, and once the inner scope ends the outer variable still holds its original value.

C# forbids a local (or a lambda parameter) from shadowing an enclosing local of the same name (CS0136). So rather than the older save/restore approach, the converter **renames** the shadowing inner variable with a `Δ` disambiguation suffix (`x` → `xΔ1`, `xΔ2`, …) and rewrites all references within that scope to the renamed identifier. The outer variable is untouched, so its value is naturally preserved. For example:

```go
func sumWithLenLocal(buf []int) int {
    total := 0
    len := len(buf)       // a local named like the built-in, shadowing it
    for i := 0; i < len; i++ {
        total += i
    }
    return total + len
}
```

converts to:

```csharp
internal static nint sumWithLenLocal(slice<nint> buf) {
    nint total = 0;
    nint lenΔ1 = len(buf);          // renamed; the built-in call stays `len(...)`
    for (nint i = 0; i < lenΔ1; i++) {
        total += i;
    }
    return total + lenΔ1;
}
```

The same `Δ` mechanism handles a local shadowing a called built-in (as above), a nested-block variable shadowing a function-level one, an IIFE/closure parameter colliding with an outer local, and a type-switch guard (`switch x := x.(type)`) whose variable shadows an enclosing one — the guard is renamed within the switch (`case T xΔ1:`) while references after the switch still resolve to the enclosing variable, matching Go's scoping.

The nested-block detection holds across a **closed sibling block**: a declaration that *follows* a nested block inside the same enclosing block (runtime `procresize`'s second `trace := traceAcquire()` after an inner `if {…}` that declared its own `trace`) is still checked against enclosing scopes. The shadow tracker's processing flag is shared across the nesting levels of a block tracker, so an inner block's cleanup must *restore* it for the still-open enclosing block rather than clear it — clearing it made the follow-on declaration skip the check and collide with the function-level local (both emitted `Δtrace` — the LAST runtime compile error, CS0136). Composition with the collision rename is suffix-based: the function-level local keeps the base name (`Δtrace` after its collision prefix), the shadows number independently of the prefix (`traceΔ1`, `traceΔ2` — a shadow name no longer collides, so it takes no `Δ` prefix). (Guarded by the `GlobalShadowedByLocal` extension `nestedBlockShadow` — the three bindings verified by value vs Go.)

A local shadowing a **same-named package function it calls in its own initializer** — `signame := signame(gp.sig)` (runtime `panic.go`) — renames the same way. Go starts the shadow *after* the initializer, so the call resolves to the function; C# scopes the local over its own initializer, so an unrenamed call would bind the (non-invocable) string local (CS0149). Detection is object-accurate: any identifier `go/types` resolves to the *function* while a same-named local exists means Go bound it where the local was not yet in scope — the old position guard ("call before the declaration") excluded exactly the own-initializer case. (Guarded by the `BuiltinShadowLocal` extension — a package `signame` shadowed in its own initializer, values vs Go.)

A **block-scoped `const`** that shadows an enclosing parameter or variable is renamed the same way. `func f(ns int64) { …; const ns = 10e6; use(ns) }` (runtime `notetsleep_internal`) is legal in Go but the inner `const ns` and the param `ns` both emit as `ns` in C# (CS0136). A const is tracked separately from variables — its `go/types` object is a `*types.Const`, not the `*types.Var` the scope stack records — so the shadow-rename pass had ignored it; it now records a shadowing const (detected by the same by-name enclosing-scope check) and rewrites its declaration and every use to `nsΔ1`, leaving the enclosing `ns` untouched. Only a *shadowing* const is renamed (a plain block const keeps its name, no churn). (Guarded by the `ConstShadowsParam` behavioral test — the inner uses bind the const value, the outer uses bind the param.)

Renaming depends on correctly identifying which declarations are *function-level* — the set a nested variable of the same name must avoid (C# forbids the nested one even when the function-level one is declared *later*). A `for init; …` loop's `:=` variable, and a range `:=` key/value, are scoped to their own statement, **not** the function body, so they are deliberately excluded from that set. Recording a for-loop variable as function-level (it is encountered first, in source order) would mask the real function-level variable of the same name declared afterward — `for b := …{} for b := …{} … b := newBucket(…)` — leaving *all three* emitted as `b` and colliding (CS0136). With the for-loop variables correctly treated as inner scopes, they are renamed `bΔ1`/`bΔ2` while the function-level `b` keeps its name. (Guarded by the `ForVarMasksFuncLevel` behavioral test; runtime hit this in `stkbucket`.)

The same forward-collision rule applies at **every block level, not just the function body**. C# CS0136 fires whenever a name is declared in two scopes where one encloses the other, *regardless of declaration order* — so a nested variable must be renamed if the same name is declared anywhere in an enclosing block, whether that declaration appears before or after it in source. The scope-stack walk only records declarations already seen (backward), and the function-level forward set covers only the function body; a variable declared **later in an intermediate enclosing block** would otherwise be missed. To close that gap, each block scope (function body, `if`/`for`/`range`/`switch`/`select` bodies, bare blocks, and `case`/comm-clause bodies) is pre-scanned for its directly-declared names (`:=` and `var`, excluding a control statement's own init `:=`, which is scoped to that statement) when the scope is pushed, so forward declarations are visible to the shadow check. For example, the runtime's `runGCProg` has two `for off := …` loops followed by `off := n - nbits` *in the same enclosing `for {}` body* — the block-level `off` encloses both loops, so the loop variables are renamed `offΔ1`/`offΔ2` while the block-level `off` keeps its name. (Guarded by the `ForVarMasksBlockLevel` behavioral test — distinct from `ForVarMasksFuncLevel`, where the later same-named variable is function-level; this cleared 5 runtime CS0136 in `runGCProg`/`mprof`/`runtime1`/`time`.)

The mirror image — a local shadowing a package-level **GLOBAL** — is resolved the other way: the *global reference* is qualified rather than the local renamed. C# locals are function-scoped, so a local `trace := traceAcquire()` shadows a same-named global `var trace` throughout the function, and an *earlier* read of the global binds to the not-yet-declared local (CS0841; the wrong variable regardless). Renaming the local is the fragile, entangled path (it interacts with collision renames and the shadow-rename counter); instead a use whose ident resolves to a package-level var **of this package** — while a same-named function-level local is declared — is emitted qualified with the package static class: `runtime_package.Δtrace.minPageHeapAddr`, which a local can never shadow. This is the same package-class qualifier the box-field accessor uses for a shadowed owning type (below). Runtime's `traceallocfree.traceSnapshotMemory` reads the global `trace.minPageHeapAddr` before its local `trace := traceAcquire()` (both collision-renamed `Δtrace`); the qualifier is gated so an ordinary global (no shadowing local) and the local's own uses (which resolve to the local, not the package scope) keep their bare, Go-like form — no churn. (Guarded by the `GlobalShadowedByLocal` behavioral test — a collision-renamed global and a plain global each read before a same-named local; cleared runtime's last CS0841.)

**That qualifier names the class that DECLARES the global, which under the white-box test model is not always the production one.** In a `whitebox-reference` internal variant, the emission unit is the bridge class (`md5_internal_test_package`), and a package-level declaration contributed by an internal `_test.go` lives *there* — the production class holds only the production declarations. Qualifying unconditionally with `<pkg>_package` therefore names nothing whenever the shadowed global comes from a test file (`CS0117`): `crypto/md5`'s `benchmarkSize` opens with the idiomatic `buf := buf`, whose package-level `buf` is declared in `md5_test.go`, and it emitted `md5_package.buf`. The qualifier is now chosen by where the object is declared — the bridge class for a `_test.go` declaration, the production class otherwise — so both halves of a white-box compilation stay addressable from the same function. Production emission is untouched (the override is empty outside the test variant), so this is zero-churn for the corpus; `crypto/md5`'s banked suite is its operational guard.

Two subtleties complete this for loops whose variable's box hoists before the loop. **A hoisted loop-variable box is block-scoped in C#, one per name per container.** A loop variable that escapes to the heap *and whose box is emitted before the loop* — today that is a string/int/chan/func **range** variable, or the legacy fallback of a `for i := …` clause variable referenced by a *clause* func literal; every other case boxes per-iteration inside the body (slice/array/map ranges via the deferred range-var box, and `for` clause variables via the per-iteration carrier rewrite — Go 1.22 semantics, see [Labeled Control Flow and Loop Variables](#labeled-control-flow-and-loop-variables)) — is emitted as a `ref var i = ref heap<…>(out var Ꮡi)` declaration hoisted into the *enclosing container* (function body, block, or switch/select clause) — see [Pointers](#pointers) — so other loops in that container that reuse `i` genuinely collide with it, unlike the ordinary all-loop-scoped case above. Loop variables are therefore grouped **per container and name**: the first whose box *actually claims a container-level name* is the keeper and keeps its name; every other direct-child loop variable with that name in the same container is force-shadow-renamed. The claim test mirrors the emission exactly — the var escapes AND is not inherently heap-allocated (a pointer/slice/map/chan/interface/func var is already a reference and gets no box) AND the box actually hoists (per the split above). A group with no claiming var is untouched, so ordinary same-named sibling loops keep their Go names — a claiming sibling would otherwise emit a *duplicate hoisted box* in the same scope (CS0128), and a non-claiming sibling's loop-scoped variable (or deferred in-body box) nests inside the block that owns the box name (CS0136). (The historical motivating cases — runtime `typesEqual`'s `for i := 0` pair inside one switch case and `runqputslow`'s three `for i := …` loops — now box per-iteration inside their bodies and no longer claim container names at all; `EscapedLoopVarSiblingIndex` keeps guarding the sibling grouping and was re-baselined to the per-iteration shape.) A function-body-level keeper is additionally recorded as function-level (so non-loop uses elsewhere shadow-rename as before), but never masks a real function-level declaration — preserving the `ForVarMasks…` invariant above. A name group with no escaped variable is untouched (loop-scoped in C# too — no churn).

**Escape analysis marks only the arg's storage ROOT, not every identifier in a pointer argument.** Passing an expression to a pointer parameter escapes the storage the pointer refers to — the *peeled root* of a literal `&expr` (through parens, field selectors, index expressions, and derefs), or the bare identifier itself. An identifier appearing merely in a *subexpression* of the argument contributes a value, not its own address: in `xs[i].link(&xs[i+1])` or `typesEqual(tin[i], vin[i], seen)` the container (`xs`/`tin`'s elements) escapes but the index `i` does not. The old contains-anywhere check heap-boxed every such loop index — a spurious allocation on a hot path (Go keeps these in registers), gratuitous `Ꮡi` machinery in the emitted code, and the very duplicate-hoist collisions the grouping above then had to resolve (`typesEqual`'s pair now emits two plain `for (nint i = 0; …)` loops, no boxes, no renames). A direct `&i` anywhere — including nested inside a larger argument — is still caught independently by the address-of analysis. **And a renamed variable used as an LHS index/map key is rewritten there too.** An assignment `a[i] = …` / `m[ns] = …` / `p.f[k] = …` reassigns the *root* (`a`/`m`/`p`); the index/key expression is a separate value, so a shadow-renamed variable used there (`a[iΔ1]`, `m[nsΔ1]`) must be rewritten by descending the target's index/selector/deref chain and renaming each index. Missing this is a *silent* bug — the LHS key kept the enclosing variable's name, so `m[ns] = nsΔ1*100` wrote to the wrong key with no compile error — as well as a CS0136/CS0165 once the loop variable itself is renamed. (Both guarded by the `EscapedLoopVarSiblingIndex` behavioral test — the array case would not compile and the map case would silently return the wrong value without the pair, its `boxedSiblings` extension covers two genuinely-escaping siblings in one switch case (both take `&i`; first keeps the name, second renames), and its `caseSiblings` extension proves the index-only pair stays UNBOXED; cleared the 2 `runqputslow` CS0136, a CS0841, and the 2 `typesEqual` CS0128.) The target-chain descent also visits a **method-call receiver** in the chain — `x.ptr().Value.next = …` (runtime `stackpoolalloc`, where the loop `x` is renamed `xΔ1` because a func-body `x` is declared after the loop). The `x` is buried inside the `x.ptr()` call, past the selector/index steps, so without visiting the call the use kept the raw `x` — read before its (later) declaration → CS0841, or a silent wrong bind. Visiting the whole call renames its receiver and argument identifiers (the call's result is the navigated base, so the descent stops there). (Guarded by the `ShadowedVarMethodCallLHS` behavioral test — write-through through the method verified vs Go; cleared the `stack.cs` CS0841.) **A TYPE ASSERTION in that chain is a navigation step too, and its absence stopped the descent dead.** `n.Values[0].(*ast.CompositeLit).Type.(*ast.ArrayType).Len = nil` — `go/types`' `generate_test.go`, inside `ast.Inspect(f, func(n ast.Node) bool { switch n := n.(type) { case *ast.ValueSpec: … } })`, where the case variable shadows the literal's `n` parameter and is renamed `nΔ1`. The assertion was in no arm of the descent switch, so *everything* below it went unvisited: the chain's root ident kept its raw name and bound the enclosing `ast.Node` parameter, emitting `(~n).Values[0]…` — `~` applied to an interface, CS0023 — while the `if` condition one line above, an ordinary expression, renamed correctly. `getIdentifier` does not see through an assertion either, so unlike a paren-rooted target there is no `reassignVar` fallback to catch the root. The descent now visits the assertion's **operand subtree** whole (the treatment the method-call arm already uses): it carries every ident that needs the rename — the root and any index below it — while the asserted *type* carries none. Where the raw name still type-checks this was a silent wrong bind rather than a compile error, which is why the guard compares values against Go rather than merely compiling. (Guarded by the `AssignThroughTypeAssert` behavioral test — the `go/types` witness, a renamed loop index *below* the assertion in the same chain, the compound-assign form, an assertion at the very root of the target in a `default` arm, and a chain-read control that already worked; the pre-fix converter regresses five emission sites, four of them CS0023.)

The reverse collision — a package **method named like a built-in** — needs the opposite treatment. In Go a method `func (b *pageBits) clear()` and the universe `clear` built-in coexist: the method is only ever reached as `b.clear()`, while a free `clear(s)` is always the built-in. But the method is emitted as a `clear(this ref pageBits)` extension on the package's static class, and C# member lookup binds that same-class member for an *unqualified* free `clear(s)` call — shadowing the using-static `go.builtin.clear` and failing (`CS1620`/`CS1503`). So a built-in call whose name the package also declares as a method/function is emitted **qualified** — `builtin.clear(s)` — which resolves to the golib built-in regardless of the same-class shadow; the method call stays `b.clear()`. (This also required golib to gain the Go 1.21 `clear` built-in itself, in slice/span/map forms — plus an `IMap<TKey, TValue>` overload for a **named map type's value**: the generated wrapper implements `IMap<K,V>` and forwards to the shared underlying map, so `clear(h)` on an `http.Header`-style named map empties the caller's storage, and a nil named map stays a no-op (net/http/httputil's `clear(h)`, CS0411 without it). Guarded by the `ClearBuiltinShadow` behavioral test — including a named-map value cleared through an alias and a nil named map; runtime hit the original shadowing on `pageBits.clear`/`sweepClass.clear`, ~11 errors.)

For a **function-literal parameter** that shadows an enclosing local, the rename must reach the parameter *declaration* itself, not just the body: `run(func(n int){ … n … })` where an outer `n` is in scope emits `run((nint nΔ1) => { … nΔ1 … })`. The body's uses already resolve to `nΔ1`; if the signature still declared the bare `n` (the raw name), the body's `nΔ1` would be undeclared (CS0103). The parameter name in the emitted lambda signature therefore comes from the same shadow-aware identifier mapping as the body (the raw name when nothing is shadowed, so plain function types and non-shadowing parameters are unchanged). (Guarded by the `ClosureParamShadow` behavioral test; the runtime hit this pervasively on `mcall`/`systemstack(func(gp *g){…})` where the closure's `gp` shadows an outer `gp`, ~40 CS0103.)

Conversely, a **local that shadows a *pointer parameter*** must not inherit the parameter's special emission. A deref-aliased pointer parameter is `ж<T> Ꮡp` with `ref var p = ref Ꮡp.Value`, so passing it whole to a `*T`-expecting function emits its box `Ꮡp`. But a *local* `t` shadowing a `t *T` parameter (`func mapKeyError2(t *_type, …){ … var t *_type; … }`) is a plain pointer local — passing it should stay `use(tΔ2)`, not `use(ᏑtΔ2)` (the spurious `&` references an undefined `ᏑtΔ2` box → CS0103). The bug was that the "is this a parameter?" check matched by *name*, so the shadowing local was misclassified; it now verifies the resolved object is genuinely one of the function's parameter objects, not just a name match. (Guarded by the `ShadowedPointerParam` behavioral test; runtime hit this on `mapKeyError2`/`interhash`'s inner `var t *_type`, ~11 CS0103.)

### Type-vs-Method Name Collisions

Go keeps types and methods in separate namespaces, so a package may legally declare both a type `foo` and a method `foo` on some receiver. In C# both land in the same package class — the nested type and the `[GoRecv]` extension method — where a type and a method cannot share a name (CS0102). The converter resolves this by `Δ`-prefixing the **type** (`Δfoo`) while the method keeps its core-sanitized name (`foo`), so they no longer collide.

This needs an extra step when the colliding name is also a **golib reserved word** (`slice`, `array`, `channel`, `map`, …). Such a name is `Δ`-prefixed *anyway* — to avoid the golib runtime type (`slice<T>` etc.) — so the method too becomes `Δslice`, and the plain `Δ` no longer separates type from method. In that case the converter appends the type marker `ᴛ` to the **type** only, giving it a name distinct from the method:

```csharp
[GoType] partial struct Δsliceᴛ { … }                          // Go `type slice struct{…}`
[GoRecv] internal static Δsliceᴛ Δslice(this ref builder b, …) // Go `func (*builder) slice(…)`
```

Only the type side is renamed; the method (and every call site and go2cs-gen-generated pointer-receiver overload) stays `Δslice`. This is deliberate: the go2cs-gen generators compute method names independently, so renaming the *method* would desync them — renaming the *type* keeps the converter and generators in agreement (the generators read the type name from the emitted C# syntax/attributes). This mirrors the Go runtime's `type slice struct{…}` (the GC slice header) versus `func (*userArena) slice(…)`.

A **struct field** named like a colliding package-level identifier is *not* renamed: a field is struct-scoped (`g.trace` does not collide with a package type/method `trace` in C#), so the field declaration keeps its core-sanitized name (`trace`). The box-field accessor static the `TypeGenerator` emits for it is therefore `g.Ꮡtrace` (the `Ꮡ`-prefixed declared member name). The converter's `&g.field` address form (`Ꮡg.of(g.Ꮡtrace)`) must use that **declared** field name — it derives the accessor member from `getCoreSanitizedIdentifier` plus the type-colliding rename, *not* from the general identifier path that applies the package-level collision `Δ`-rename. Using the latter would emit `g.ᏑΔtrace`, which has no matching generated static (CS0117). Reserved-word fields keep their `Δ` (the field really is declared `Δarray` for a field named `array`), so the accessor is `Ꮡ`+the declared name in every case. (Guarded by the `CollisionFieldBoxAccessor` behavioral test; runtime hit this on `g`/`m`/`p`'s `trace`/`stack`/`p` fields, ~20 CS0117.) The generated accessor's **accessibility** matches the *field's* (its exportedness), not the field type's name — an exported field `Fun [1]uintptr` (C# `array<nuint> Fun`) yields a **public** `ᏑFun`, so another package's `other.of(ITab.ᏑFun)` can reach it; deriving the scope from the type's simple name (`array` → lowercase → `internal`) would make the cross-package accessor unreachable (CS0117 in runtime's `iface.go` walking `abi.ITab.Fun`).

One case *does* rename the field: when its name equals its **enclosing type's** name *and* that type is itself `Δ`-renamed for a type-vs-method collision. internal/trace's `type Label struct{ Label string }` sits alongside `func (e Event) Label() Label`, so the type becomes `ΔLabel`; the field, whose name equals the type, is renamed to differ (CS0542 — a member cannot share its type's name). The existing rename prefixed a single `Δ`, but that yields `ΔLabel` — *equal* to the renamed type, so the collision persisted. `typeCollidingFieldName` now **doubles** the marker (`ΔΔLabel`) when the name is a package-level collision, exactly as it already did for the keyword-family case (a reserved-word type is `Δ`-renamed too). Deterministic from the name, so the field declaration, the keyed composite-literal key, and every access site all agree:

```csharp
[GoType] partial struct ΔLabel {                 // Go `type Label struct{ Label string }`
    public @string ΔΔLabel;                      // field name == type name, doubled to differ
}
… new ΔLabel(ΔΔLabel: e.label, …)                // composite key
… l.ΔΔLabel                                      // access
```
(Guarded by `FieldNameTypeMethodCollision` — a `Label` field in a `Label` struct with a colliding `Label()` method, read/written through a value, the method result, and a composite literal.)

The double must also apply **across packages**. `typeCollidingFieldName` keys the double on the current package's `nameCollisions` map, which is populated only for the package being converted — so a **cross-package** access of such a field (internal/trace/testtrace reading a `Label`'s field) emitted the SINGLE-marker `l.ΔLabel` against the declaration's double `ΔΔLabel` — CS1061. The access site now consults the FIELD'S OWN package: `fieldTypeIsRenamed` derives the enclosing named type from the selector and asks `packageHasMethodNamed(type.pkg, type.name)` (a cached per-package scan of every func/method name — a type-vs-method collision Δ-renames the type), threading the result through a new `fieldTypeIsRenamed` ident context so `convIdent` upgrades the single marker to the double for the foreign case (the in-package case already doubled via `nameCollisions`, and its result is left untouched). [CNR](Glossary.md#cnr) byte-identical (the pattern is absent from the single-package corpus except the guard). This is the FIELD-access counterpart to the cross-package renamed **type-reference** substitution (getCSharpTypeName / getScopeCheckedTypeName, further below) — that one covers naming the renamed *type*, this covers accessing its *field*; internal/trace/testtrace needs both. (The type-reference half — `trace.Time`/`Event`/`Stack` in a func signature, or `*time.Location` as a box element — is **also resolved**: a fresh full reconvert renders `traceꓸTime`/`ж<timeꓸLocation>` correctly through the `convertToCSFullTypeName`→`getAliasedTypeName` path described in *Foreign renamed types reference the recorded imported-type alias* below. It was mis-diagnosed as a still-open root off a stale [overlay](Glossary.md#overlay) whose `importedTypeAliases` were not populated.) (Guarded by the `CrossPkgUser`/`CrossPkgLib` extension — `CrossPkgLib.Marker`, a `Marker` field in a `Marker` struct alongside a `Sensor.Marker()` method, its field read across the assembly boundary through an inferred-type value; vs Go.)

The same struct-scoped rule applies to a **keyed composite-literal field name**. `Frame{funcInfo: f}`, where the field `funcInfo` is named like a colliding package type/method (declared unrenamed as `funcInfo`), must emit the C# initializer key `funcInfo:` — the package-level `Δ`-rename that `convExpr` would apply yields `ΔfuncInfo:`, which is not a parameter name of the generated constructor (CS1739). `convKeyValueExpr` therefore emits a struct-field key whose name collides at package level via `getCoreSanitizedIdentifier` (the declared name), not the general identifier path. (Same `CollisionFieldBoxAccessor` test; runtime hit this on `Frame{funcInfo: …}` in `symtab`.)

The *type* half of the same accessor (`receiver.of(Type.Ꮡfield)`) needs care too. Go code routinely names a local after its own type — `m := getg().m`, where `m` is a `*m` — so taking the address of one of its fields (`&m.park`) emits `m.of(m.Ꮡpark)`, in which the bare type reference `m` binds to the **variable** `m` (a `ж<m>`, which has no `Ꮡpark`) instead of the type (CS1061). Because a converted struct is nested in its package's static class, the converter qualifies the type with that class — `m.of(runtime_package.m.Ꮡpark)` — which a same-named local cannot shadow. A bare `m` (binds the variable) and a `go.m` (the struct is not a direct member of the `go` namespace) both fail; the package-class qualifier is the correct form. This is applied **only on a collision** (the `.of()` receiver variable's name equals the type's simple name), so every other box accessor keeps its un-namespaced, Go-like form — no golden churn. (Guarded by the `VarNamedAsType` behavioral test; runtime hit this on `m`/`Δp` locals taking field addresses, ~9 CS1061.)

The same collision fires when the receiver is that variable's **lambda capture**. Inside a closure the captured variable renames to its capture copy (`mʗ1`), so the receiver-equality check alone misses it — but the *enclosing* local `m` is still visible to the C# lambda, so the accessor's bare owning-type reference binds to it all the same: runtime `rwmutex.lockSlow`'s `systemstack(func() { …; notesleep(&m.park) })` emitted `mʗ1.of(m.Ꮡpark)` → CS1061. `boxAccessorType` therefore also qualifies when the receiver is the type name plus the capture marker (`typeName + ʗ…`), yielding `mʗ1.of(runtime_package.m.Ꮡpark)`. (Guarded by a further extension to `CollisionFieldBoxAccessor` — `capturedLocalNamedAfterType`, a type-named local field-addressed inside a capturing closure, write-through verified vs Go; cleared runtime rwmutex's 2 CS1061, 91 → 89.)

The type half also needs the **type-vs-method collision rename** (above). When the accessor's owning type is itself a colliding name — `type funcInfo` versus a method `func (f *Func) funcInfo()`, so the type is declared `ΔfuncInfo` — taking the address of one of its fields must use the renamed type (`Ꮡ(f).of(ΔfuncInfo.Ꮡnfuncdata)`); a bare `funcInfo.Ꮡnfuncdata` binds to the package's static `funcInfo` method group (CS0119). The `boxAccessorType` helper applies the `Δ`-rename to a bare same-package collision name before its receiver-shadow check (the renamed name no longer matches a raw-named local, so the two disambiguations compose). (Guarded by an extension to `CollisionFieldBoxAccessor` — a global whose type is the collision type; runtime hit this in `symtab`'s `pcdatastart`/`funcdata`.)

A **collision-renamed owning type is qualified unconditionally**, not just when it equals the `.of()` receiver — because a Go local named after its type is renamed to the *same* `Δ`-name, so such a local **anywhere in the function** shadows a bare `Δp.Ꮡfield` (C# locals are function-scoped). Runtime's malloc `persistentalloc1` does `persistent = &mp.p.ptr().palloc` and then declares a local `p` further down (renamed `Δp`); the accessor `(~mp).p.ptr().of(Δp.Ꮡpalloc)` bound its bare `Δp` to that later local — CS0841 (use-before-declaration), and CS1061 regardless (the local's type has no `Ꮡpalloc`). The receiver (`(~mp).p.ptr()`) is not the colliding local, so the receiver-name check missed it. `boxAccessorType` now qualifies whenever the type name is `Δ`-prefixed (a type is never shadow-renamed — types are package-level — so a `Δ`-prefixed accessor type is always a collision rename), emitting `(~mp).p.ptr().of(runtime_package.Δp.Ꮡpalloc)`. Qualifying is value-identical to the bare form when nothing shadows, so it is safe to apply to every collision-type accessor. (Guarded by a further extension to `CollisionFieldBoxAccessor` — `localShadowsCollisionType`, a local named after the collision type declared after the accessor; cleared runtime malloc's CS0841 plus two mheap `Δp.Ꮡgcw` CS1061 of the same shape, 148 → 145.)

The three receiver-shadow arms above (`.of()` receiver equals the type, its capture, its box) and the collision-rename arm are all **special cases of one general rule**: a box accessor's bare owning-type spelling `Type.Ꮡfield` binds to **any** same-named variable that C# has in scope, and C# scopes a local to its whole enclosing block regardless of where it is declared or whether it participates in this accessor at all. So `boxAccessorType` also qualifies whenever a variable of the type's name is declared **anywhere in the current function** — receiver, parameters, results, or a local at any nesting depth (func literals included), collected into `funcScopeVarNames` during variable analysis. The general case, unrelated to any type-vs-method collision, is vendored poly1305 under `-tags purego`: `mac_noasm.go` declares `type mac struct{ macGeneric }`, and `func (h *MAC) Sum(b []byte)` declares `var mac [TagSize]byte`, so reaching the promoted-embed method `h.mac.Sum(&mac)` spelled `Ꮡ(h.mac).of(mac.ᏑmacGeneric)` in which `mac` bound to the `array<byte>` local (CS1061 ×2). It errored precisely in `Sum`/`Verify` (which declare that local) and **not** in `Write` (which does not) — confirming the diagnosis. Qualifying is always value-correct: Go guarantees the reference is unambiguous, and inside a scope that shadows the type the type is simply unreachable by that bare name, so every bare occurrence the emitter produces is meant as the type. Emitted forms (from the `LocalShadowsEmbedHopType` guard):

```csharp
Ꮡ(h.acc).of(acc.Ꮡinner).Add(p);                                             // Write: no local named `acc` — stays bare
Ꮡ(h.acc).of(main_package.acc.Ꮡinner).Store(Ꮡacc);                           // Sum: `var acc [4]byte` shadows the type — qualified
Ꮡ(d.deep).of(main_package.deep.Ꮡacc).of(main_package.acc.Ꮡinner).Store(Ꮡdeep); // Verify: both hop types shadowed (nested-block locals)
```

This is a **pre-existing** shadow class the converter always had latent; adopting `-tags purego` for the standard-library conversion (see *The standard-library conversion applies `-tags purego`* below) is what first reached the poly1305 code that exercises it — unblocking poly1305 and its 17 dependents (`chacha20poly1305` → `crypto/tls` → `net/http`, `net/rpc`, `net/smtp`, `expvar`). (Guarded by `LocalShadowsEmbedHopType`, whose `Write`/`Sum`/`Verify` discriminate qualified-vs-bare by whether the method declares the shadowing local; `CollisionFieldBoxAccessor`'s `boxRefCapturedValueNamedAfterType` golden was re-baselined — it previously compiled `Ꮡw.of(w.Ꮡpark)` only because C#'s identical-simple-name rule happened to bind the type; it now qualifies uniformly like every other shadowed accessor, a strict improvement with identical runtime output.)

A related case is the **box name of a shadow-renamed receiver/parameter**. A deref-aliased pointer (a receiver or a `*T` parameter) is emitted as `ref var <name> = ref Ꮡ<raw>.Value` — the `Ꮡ` companion always keeps the **raw** Go name, even when the value alias is shadow-renamed for a collision (`func (p *cpuProfile) add()` where `p` collides with the type `p` → `ref var Δp = ref Ꮡp.Value`). When a pointer-receiver (capture-mode) method is then called on that receiver/parameter, the call routes through the box, and that box reference must use the raw name `Ꮡp` — the value alias `Δp` would yield `ᏑΔp`, which is not in scope (CS0103). The converter builds the box from the raw identifier name (not the shadow-renamed value form), but only when they differ — so non-renamed receivers are unaffected (no churn).

The same raw-box-name rule applies when such a shadow-renamed pointer is **captured by a closure** (where the value alias is referenced through its box, since the `ref`-local can't be captured — see the *box-ref* section below). A value use inside the closure becomes `Ꮡp.Value.n` and a field-address use `Ꮡp.of(T.Ꮡn)` — both rooted at the raw box name `Ꮡp`, never the renamed `ᏑΔp`. The field-address form (`&p.field`) routes through the box-ref address path rather than the generic pointer-variable path: that generic path would prepend `Ꮡ` onto the closure's box-deref read (`Ꮡp.Value`), yielding a double-boxed `ᏑᏑp.Value` (CS0103). Because the captured pointer's box `Ꮡp` *is* the `ж<T>`, the field address is simply `Ꮡp.of(T.Ꮡfield)` — the same form as a captured value struct. (Guarded by the `RenamedReceiverBox` behavioral test, which exercises a shadow-renamed receiver calling a capture-mode method, plus a shadow-renamed pointer parameter both read through and field-addressed inside a closure; runtime hit this on `p`/`Δp` receivers calling methods like `p.addExtra()` and on closures capturing such pointers, ~12+ CS0103.)

A closure that captures an outer variable is emitted with a snapshot copy declared before the lambda — `var sʗ1 = s;` — and uses of the captured variable inside the lambda are rewritten to that capture name `sʗ1`. The capture-name mapping is keyed by **name**, which breaks on a **self-shadowing initializer inside the closure**: runtime `mgcsweep`'s `systemstack(func() { s := spanOf(uintptr(unsafe.Pointer(s.largeType))); … })` declares an inner `s` whose initializer reads the *outer* captured `s`. Both the captured use (the RHS `s.largeType`) and the distinct inner binding were mapped to the same `sʗ3`, so the inner declaration emitted `var sʗ3 = …(~sʗ3)…` — its RHS binding to the not-yet-initialized inner variable (CS0841). The fix records the captured **object** alongside the name, and applies the capture name only when an ident resolves to that exact outer object; the inner binding falls through to its own (shadow-renamed) name. The emission is `var sΔ1 = spanOf(…(~sʗ3)…)` — the inner `s` shadow-renamed to `sΔ1` (distinct from the capture `sʗ3`), its RHS correctly reading the captured `sʗ3`, and later uses of the inner `s` using `sΔ1`. Because the object check passes for every non-shadowing capture (the ident *is* the captured variable), it changes nothing outside this self-shadow case (zero golden churn). (Guarded by the `ClosureSelfShadowCapture` behavioral test — a captured pointer with an inner `s := f(s)` in a `systemstack`-shaped call-argument closure, output verified vs Go; cleared runtime `mgcsweep`'s CS0841.)

The same rule applies to an **escaping local** whose address is taken — `var p _panic; … preprintpanics(&p)` in runtime's `gopanic`, where `p` collides with the type `p`. The heap allocation is `ref var Δp = ref heap(new _panic(), out var Ꮡp)`, so the box is `Ꮡp` (raw) and `&p` must emit `Ꮡp`, not `ᏑΔp`. **Crucially, the box-name rule is keyed to the rename *kind*, because the two kinds name their boxes differently:** a type-**collision** rename prepends the marker (`p` → `Δp`) but keeps the raw box (`Ꮡp`), whereas a nested-scope **shadow** rename appends the marker plus a counter (`i` → `iΔ1`, `iΔ2`) and keeps the *shadow* box (`ref var iΔ1 = ref heap<nint>(out var ᏑiΔ1)`, so `&i` correctly emits `ᏑiΔ1`). The converter therefore rewrites to the raw name *only* when the alias is exactly `Δ`+rawname (the collision form); a shadow-renamed or non-renamed var keeps its existing box name. (Guarded by the `CollisionRenamedLocalBox` behavioral test, with `ForVariants`/`NestedVarShadow` covering the shadow-rename form left unchanged.)

#### A nested closure must not clobber the enclosing closure's capture state
The per-lambda conversion state — `conversionInLambda` (are we inside a closure body?) plus the capture-name maps (`currentLambdaVars`/`currentLambdaVarObjs`) — is what makes closure-body emission rewrite captured references to their box/copy forms: a captured local `s` reads as `sʗ1`, and the current method's **direct-ж receiver** (`func (s *Stmt) …` emitted `this ж<Stmt> Ꮡs`, whose body alias `ref var s = ref Ꮡs.Value` is a `ref`-local that **cannot** be captured by a C# closure) reads through its box as `Ꮡs.Value`. That state was *set* on entering a closure but **reset to `false`/`nil` on exit**, not restored — so a closure that contains an **inner** closure had its state wiped the moment the inner one finished, and every reference in the *outer* closure body **after** the inner one fell back to the bare, un-rewritten name. For a receiver field-read that is a bare ref-local capture — `database/sql (*Stmt).QueryContext`'s `s.db.retry(func(){ …; rows.releaseConn = func(err){…}; if s.cg != nil { … } })`, where `s.cg` sits after the inner `releaseConn` closure — the emission was `s.cg` (CS8175, "cannot use ref local `s` inside an anonymous method/lambda"); the equivalent captured-local case silently split a variable between its bare form and its `ʗ1` copy within one closure. The fix makes `enterLambdaConversion`/`exitLambdaConversion` a proper **LIFO save/restore stack** (`conversionStack`): entering pushes the current state and installs fresh state; exiting **restores the enclosing closure's** state instead of resetting. A closure at top level still restores to `false`/empty (unchanged), so the change is inert except where a closure body continues after a nested closure — there the receiver box-read (`Ꮡs.Value.cg`, `Ꮡs.Value.cg.txCtx()`) and the captured-local copy name are now applied consistently across the whole body. (Guarded by the `NestedLambdaReceiverField` behavioral test — a direct-ж receiver method whose closure holds a nested closure followed by a non-call receiver field read, a field-method call, and another field read, all verified to render `Ꮡs.Value.<field>` and output-compared vs Go; cleared `database/sql`'s 2×CS8175 and re-baselined `DeferValueFieldPtrReceiver` whose defer-then-body sequence exercises the same restore.)

### Test-variant name coherence: production names are pinned, test-side method declarators Δ-rename

The `-tests` pipeline re-analyzes the package over the **whole variant universe** (production files + `_test.go` files) but only **emits** the test files — the production `.cs` on disk were converted from the production-only universe and recompile into the test assembly as-is. Production symbol names are therefore **immutable** in a test-variant analysis: any collision a test file introduces must resolve by `Δ`-renaming the **test-side declarator**, never the production element. Two shapes (strings/sort blockers B2/B9), both resolved in `performNameCollisionAnalysis`:

- **A test-file METHOD over a production element's name (B2).** strings' export_test.go declares `func (r *Replacer) Replacer() any` — the ordinary type-vs-method resolution (above) would Δ-rename the *type*, but the production `replace.cs` on disk keeps `Replacer`, so the assembly split into two disagreeing halves (CS0102 `strings_package` already contains `Replacer` + CS0246 `ΔReplacer`). When the colliding method declarators are **all** test-declared and the element is production-declared, the element keeps its bare name (and no exported alias is registered) and the **method** Δ-renames instead; a FuncDecl colliding with a same-package element is necessarily a method (Go keeps method names in a separate namespace — any other same-scope reuse is a Go compile error). When a *production* method also carries the name, the production universe had the same collision and already renamed the element on disk, so the normal path stays consistent.
- **A test-file METHOD shadowing a dot-imported function the variant calls unqualified (B9).** Go keeps method names and dot-imported function names in separate namespaces, but both land in the C# package class's member-lookup scope, and an enclosing class's method group always wins over `using static` imports — sort_test.go's dot-imported `Sort(data)` bound example_keys_test.go's `By.Sort` extension (CS1501 ×14, plus 5 downstream method-group CS1503s the wave-2 probe had attributed to B10). A test-declared method whose name matches a foreign function the variant references **unqualified** (only unqualified sites conflict — SelectorExpr Sels are excluded, so a qualified `sort.Sort(ps)` never triggers; an unqualified foreign-function reference can only come from a dot-import) Δ-renames, and the dot-imported call keeps its bare emission, now binding through `using static`.

- **A test-file FREE FUNCTION whose emitted signature matches a production METHOD's receiver (2026-07-20).** A method emits as a C# extension method, so its receiver becomes the leading `this` parameter — and `this` does **not** participate in C# signature identity. math/big's `func (z nat) norm() nat` (nat.go) and `func norm(x nat) nat` (int_test.go) are legal Go in separate namespaces, but both emit as `norm(nat)` in `big_package`: CS0111. `resolveReceiverParameterCollisions` compares each same-named pair's *emitted* parameter list — the method's receiver type followed by its parameters, against the free function's parameters — and Δ-renames the test-side declarator. Discrimination is exact: an extra parameter (`func trim(x nat, n int)`) or a different first parameter (`func keep(n int)`) emits distinctly and keeps its plain name, as do generic declarations (type parameters keep the C# signatures distinct) and a variadic/non-variadic mismatch. Two methods can never collide this way (Go forbids redeclaring one method on one type) and two free functions cannot share a package scope, so a method/free-function pair is the only shape. When *both* sides are test-declared the FREE FUNCTION is the one renamed, so the outcome does not depend on declaration order and two colliding declarators never both become `Δ`-prefixed; a collision between two **production** declarators is deliberately left alone, since it would equally break the production-only conversion and is a different fix than test-variant coherence (the 302-package corpus compiles clean, so no instance exists).

The rename registry is **object-keyed** (`testMethodRenames map[types.Object]bool`) — the same-named production type/function keeps its plain emission at every other site — and **session-scoped**, initialized once per `-tests` conversion rather than per variant: both variants come from one `go/packages` load, so the external variant's references to an internal-variant method (the export_test pattern) resolve by object identity to entries registered during the internal pass. The declaration renames in `visitFuncDecl`, and every reference follows through `convIdent` — a METHOD name through its isMethod arm (all selector emissions funnel there), and a package-level **free function** referenced as a call target or function value through the trailing identifier path, which the receiver/first-parameter case above made reachable (without it the declarator renamed while its call sites still emitted the bare name: CS0103). The go2cs-gen `RecvGenerator` reads the emitted name, so generated `ж`-receiver overloads follow automatically. Real emissions from the probes:

```csharp
public static any ΔReplacer(this ж<Replacer> Ꮡr) { … }   // strings export_test.go `func (r *Replacer) Replacer() any` — type stays bare
@string got = fmt.Sprintf("%T"u8, tc.r.ΔReplacer());     // EXTERNAL-variant call site (replace_test.cs) follows the internal rename

public static void ΔSort(this By by, slice<Planet> planets) { … } // sort example_keys_test.go `func (by By) Sort(planets []Planet)`
new By(mass).ΔSort(planets);                                       // test-method call sites follow
Sort(data);                                                        // dot-imported production call keeps its bare emission
```

Production conversions have no `_test.go` files in their universe, so the analysis is inert there ([CNR](Glossary.md#cnr) byte-identical ×402). (Guarded by `TestTestVariantPinsProductionTypeAgainstTestMethodCollision` — declaration, internal call site, external call site, and the pinned type — and `TestTestVariantRenamesTestMethodShadowingDotImportedFunction` — rename + bare dot-imported call, with never-referenced and qualified-only same-named methods as discrimination controls.)

### A NESTED package's production `GoImplement` record anchors to the production metadata file

An EXTERNAL test variant's collected `GoImplement` records are split across **two** anchor files, because the go2cs-gen `ImplementGenerator` hosts its output in the **first class** of the attribute-bearing file: a record whose generated adapter must be a member of the test package class goes to `package_info_external_test.cs` (whose first class is `<pkg>_test_package`), while a record that generates a partial/adapter on the **production** class stays with the production-anchored `package_test_info.cs`. `isTestAnchoredImplementRecord` decides which, by testing the implementer name against the production class's qualifier.

The anchor is a **file-level** property — `GetFirstClassName` reads the first `ClassDeclarationSyntax` in the compilation unit — so two required anchors mean two files; this is a workaround for the generator's positional contract, not an intrinsic need. The external unit is written only when the variant actually records test-anchored attributes, so utf8-class packages keep their single-file shape. It carries **no** `[GoPackage]` (the attribute-bearing partial stays in `package_test_info.cs`, CS0579) and **no** `global using` aliases (they must be declared once per compilation, CS1537).

⚠ **The `_test.cs` suffix on `package_info_external_test.cs` is load-bearing.** It is what excludes the file from the *production* project, via the shared `csproj-template.xml` `<Compile Remove="*_test.cs;…">` glob and `productionCSFiles`. `package_test_info.cs` does *not* match that glob, which is why it needed its own explicit entry in the template — and why the external unit was originally named `package_info_test.cs` (2026-07-18) to ride the glob for free. That name was a near-anagram of `package_test_info.cs`, and the two sorted adjacent to `package_info.cs` in every converted package directory; it was renamed to `package_info_external_test.cs` on 2026-07-21 ("external test package" is Go's own term for `package <name>_test`). Any future rename must keep the `_test.cs` suffix, or add an entry to the shared template — which re-emits, and so churns, every behavioral `.csproj`.

The live records qualify the implementer **namespace-relative — WITHOUT the `go.` root**: math/rand's external `rand_test` casting `*rand.Rand` to `io.Reader` records `ж<math.rand_package.Rand>`. The test formerly compared only the BARE (`rand_package.`) and fully-rooted (`go.math.rand_package.`) forms, so a **top-level** package matched by accident — its relative qualifier *is* the bare form (`sort_package.IntSlice`) — while every **nested** package (`math/*`, `text/*`, `net/*`, `encoding/*`, `container/*`, `hash/*`, `crypto/*`) matched neither and mis-anchored to the test file. The generator then emitted a SHORT `StructName` (non-foreign structs are emitted unqualified) inside `rand_test_package`, producing `ж<Rand>` where no `Rand` is in scope — while the converter's own cast site already assumed production anchoring (`new rand.RandжReader(r)` against `using rand = go.math.rand_package;`), so the pair failed as two CS0246s in the generated `.g.cs`. The relative qualifier is now recognized alongside the other two, matching the function's stated contract.

⚠ **`package_info_external_test.cs` and `package_test_info.cs` are MERGE-PRESERVING.** After any change to this routing, DELETE both files *and* the package's `Generated/` directory before re-running the pipeline — otherwise a stale record persists in **both** anchors and masks the result.

### Test suites REFERENCE the production project instead of recompiling it

The original `-tests` model — recompile the production `.cs` into the test assembly — duplicates the production types. That is harmless until another referenced assembly surfaces one of those types in its API: `strings.ToLowerSpecial(unicode.SpecialCase, …)` and `hash.Hash : io.Writer` name the type in the production assembly, while the test source binds a distinct recompiled copy. No compile-set adjustment repairs that identity split.

`-tests` therefore selects among **three test-project models** (`selectTestProjectModel`, recorded as `testProjectModel` in the manifest):

- **`reference`** — a black-box-only suite references the colocated production project and emits only the external test package into the test assembly.
- **`whitebox-reference`** — a suite with an internal variant still references production, but emits the internal `_test.go` declarations into a separate friend-assembly bridge. This keeps the production assembly as the sole identity while preserving access to Go-unexported members.
- **`recompile`** — the original same-assembly shape, retained only as the deterministic fallback when test-contributed metadata genuinely has to mutate a closed production type.

Both reference models bind the package under test as an ordinary imported package: exported aliases and implementation metadata load from the colocated `package_info.cs`, its types render package-qualified, and `isSameAssemblyPkg` is false. Their `package_test_info.cs` is a **test-class-only metadata anchor**; it never declares a local production partial.

The white-box extension has five coupled parts:

1. The normal production scan already reads build-selected same-package `_test.go` files to stabilize alias-shadow spelling. That same cheap scan now reports whether an internal test file exists. Only then does the production `.csproj` emit `<InternalsVisibleTo Include="$(AssemblyName).tests" />`; packages with no internal tests remain byte-stable.
2. Internal test files emit into `<name>_internal_test_package`, with `using static <namespace>.<name>_package`. Production declarations remain untouched. Test-host registrations retain the Go package name in the manifest but target this bridge class.
3. `go/packages` loads production, internal and external test variants together. An external test reference is routed to the bridge only when its `go/types.Object` belongs to the production import path **and** its declaration position is in `_test.go`; production objects and same-spelled unrelated declarations keep their ordinary route. This is how `io_test` reaches `ErrInvalidWrite` from `export_test.go` without source rewriting or a generated alias contract.
4. Test-contributed implementation adapters are owned by test metadata anchors — a MIXED suite has **two**. The generators host output in the FIRST class of the attribute-bearing file, and a mixed white-box assembly has two classes generated code must merge into, so the B4/B5 two-file split returns in mirror image: records whose generated partial must merge with a **bridge-declared** type (a BARE record name in the internal variant's declared-name set — `splitWhiteboxVariantRecords`) anchor in **`package_info_internal_test.cs`**, whose first — and only — class is the bridge (also the bridge's single `static` declaration and its `[GoPackage]` carrier); every other record — production-qualified, foreign, or external-declared — stays in `package_test_info.cs` under the external test class. Anchoring a bridge implementer in the external class would generate a phantom empty type there instead of merging with the real declaration. Deferred adapter markers are redirected only when the exact `(struct, interface)` pair appears in a test anchor (`emittedAdapterPair`); the anchored reference is composed `<anchor>.<member>` where the member comes from the **record's** spelling (`anchoredAdapterMemberName` — `adapterStructKey` normalizes a qualified production struct to the generator's foreign `<pkg>_<Simple>` form and leaves a variant-local name bare, exactly the generator's local-vs-foreign naming split; composing from the cast site's spelling instead emitted `ParseErrorжerror` where the generator wrote `csv_ParseErrorжerror`), and each pair remembers **which** anchor file recorded it. Imported production adapters keep pointing to their defining assembly. The generator also recognizes a collision-renamed embedded value property (`ΔBuffer` for embedded `bytes.Buffer`) as the same promotion hop, and scans the current compilation for friend-bridge box-receiver extensions by **simple name** when — and only when — the struct has no local declaration (the bridge spells its box parameter through the imported alias, `this ж<Replacer>`, and the metadata-only case is precisely the one whose discovery compilation is null).
5. The metadata seed imports the production, bridge and external-test classes as needed, but its first and only declaration remains the selected test anchor. An internal-only suite's bridge is both the test class and the bridge, so the seed imports it exactly once (a second, global import of the same class is CS8933). This keeps go2cs-gen's positional anchor contract deterministic for mixed and internal-only suites.
   **A MIXED suite's `package_info_internal_test.cs` is therefore written UNCONDITIONALLY, records or not (2026-08-14).** The file is not only a metadata anchor: it is the bridge class's ONLY `public static partial` declaration. Every converted SOURCE file opens its package class bare — `partial class registry_internal_test_package {` — exactly as production and external-test sources do, with the modifier living in the metadata file; see [`package_info.cs`'s `TypeAccessibility` section](#package_infocss-typeaccessibility-section-pins-each-types-accessibility-in-source) for the same division of labour applied to types. Writing the unit only when the variant contributed bridge-anchored `GoImplement`/`GoImplicitConv` records therefore left a record-less bridge with no `static` declaration anywhere, and an internal test file declaring a method on a production type — which converts to an EXTENSION method — is then **CS1106**. `internal/syscall/windows/registry`'s whole 6-verdict suite sat behind one such line, `func (k Key) SetValue(name string, valtype uint32, data []byte) error` in its `export_test.go`. Mixed suites that appear to escape it do so *incidentally*: `sort`, `bytes` and `strings` each happen to have a go2cs-gen `RecvGenerator` file that re-declares the class `public static partial` — a generator supplying a modifier the emitter owes. A record-less bridge writes an anchor whose sections are all empty, which is what the production and external-test seeds already do in the same situation. Measured: registry moves from build-blocked to **4 of 6** (residuals `TestValues`, a raw-address array reinterpret materializing a zero-length `array<T>`, and `TestGetMUIStringValue`); guarded by `TestWhiteboxBridgeUnitIsWrittenWithoutBridgeRecords`.
6. The friend grant is **inserted after template rendering**, never as a template verb: a user-supplied `-csproj` template keeps its historical verb count and renders exactly as before (`insertFriendAssemblyAccess`, anchored on the first closing `PropertyGroup`). And the reference models' anchored metadata writes treat the anchor class as the local type scope (`metadataAnchorLocalTypes`), while the recompile model's anchored writes keep the historical production-local qualification — there the production class genuinely is local to the assembly.

**A production ALIAS whose right-hand side is ANONYMOUS is carried across with its `global using` (2026-08-14).** Go's `type CorpusEntry = struct{Parent string; Path string; Data []byte; …}` (internal/fuzz's `fuzz.go`) has no C# spelling of its own, so the production conversion LIFTS the anonymous struct to a real nested type and reaches it through a compilation-scoped alias — `global using CorpusEntry = go.@internal.fuzz_package.CorpusEntryᴛ1;` at the top of `fuzz.cs`. `global using` is scoped to ONE compilation, and a reference-model test project is a second one that does not recompile the production sources, so neither half crossed: nothing visited the declaration, nothing claimed the lift, and every test-side reference fell through to `t.String()` and emitted **raw Go syntax into a C# file** —

```csharp
internal Func<struct{Parent string; Path string; Data []byte; Values []any; Generation int; IsSeed bool}, error> fn;
```

— CS1031/CS1525/CS1003 cascades in `minimize_test.cs` and `worker_test.cs`, with all 52 of the package's verdicts behind them. `seedProductionAliasLifts` now reads the production package's own `package_info.cs` (which the test conversion already opens for its `GoImplement` pairs) and seeds **both halves together**: the alias into `importedTypeAliases`, so the test metadata file re-emits the `global using`, and the anonymous TYPE into `productionAliasLiftedTypes`, so every renderer spells `CorpusEntry` (`liftedNameFor`, consulted wherever `liftedTypeMap` was). Keying by go/types identity is exact here — production and test variants are type-checked in one `go/packages` load, so the alias's right-hand side and every test-side reference are the same `*types.Struct`.

Narrow on both axes, deliberately. Only an **anonymous** right-hand side is seeded: a named RHS already renders through its own qualified name, and aliasing it would put avoidable `global using` names into a compilation where a test-local type could collide. And only an alias the production `package_info.cs` **publishes** is seeded, so a type is never rendered under a name the test compilation cannot resolve; an unexported alias to an anonymous struct publishes nothing and keeps the pre-existing route. Guarded by `TestSeedProductionAliasLiftsCarriesLiftAndAliasTogether`, which carries both negative controls.

`internal/fuzz` builds clean afterwards (0 errors, from four parse-error families) and then stops one layer further out, on an infrastructure root that is **not** this one and is worth recording precisely: its `worker_test.go` `TestMain` calls `flag.Parse()`, and the converted `flag.CommandLine` has never been told about the host's own `--json` / `--result` / `--junit` / `-timeout` arguments, so the run dies with `flag provided but not defined: -json` before any test executes. In Go, `testing.M` registers those flags on `flag.CommandLine` before `TestMain` runs, which is what makes the same `flag.Parse()` legal there. This also **corrects** the board's attribution of the identical symptom on `go/internal/srcimporter` ("the process the host launches is not the go2cs test host"): the process IS the host — what it lacks is the flag registration.

**That second layer is now closed, host-only (2026-08-14) — `src/core/testing/TestFlagBridge.cs`, and `internal/fuzz` banks 52/52 behind it.** The host declares its OWN command line on the converted `flag.CommandLine` immediately before it invokes a converted `TestMain`, which is exactly what `testing.Init()` does for `-test.*` in Go and exactly what was missing: nothing is consumed or hidden from the parse, the names are simply *defined*, so `flag.Parse()` recognizes them. The full `-test.*` set is registered — with this run's real values for `test.run`/`test.parallel`/`test.v`/`test.short`/`test.count`/`test.timeout`/`test.shuffle` — rather than only the spellings the host was handed, because converted tests READ those flags back (`os/exec` gates on `flag.Lookup("test.run").Value.String()`, `runtime` on `flag.Lookup("test.parallel").Value.(flag.Getter).Get().(int)`); registering less would trade a parse error for a nil dereference, and it is why the typed registrars are used and not `flag.Func`, whose `funcValue` has an empty `String()` and is no `Getter`. The `flag` package is bound **late, by name**, not by project reference: Go's `testing` imports `flag`, but the generated test csproj sets `DisableTransitiveProjectReferences=true` (load-bearing against CS0576), so a `testing` → `flag` reference does not deploy `flag.dll` beside the 124 of 141 test hosts whose package does not import `flag` — measured, along with a +33% build cost on every test project — and it is not needed, because the converted `flag` package is present in a test compilation **iff** that package imports it, which is precisely when `flag.CommandLine` is observable at all. A name the test package already defined is skipped, since the converted `FlagSet.Var` panics on redefinition. Go's `M.Run` also parses when `flag.Parsed()` is false; that is deliberately NOT mirrored, because no member of the class needs it and an unconditional parse would newly reach `ExitOnError` for packages that merely reference `flag`.

**Fallback is based on mutation, not merely on a production-qualified record.** Pointer/value adapters and the shared `T → ж<T>` boxing route are relocatable. A structural conversion involving a production type, or a numeric conversion whose two operands are both production types, would require an operator on a closed referenced type; `recordsRequireProductionMutation` returns `errProductionAnchoredRecords`, and the already-loaded variants are re-emitted once under `recompile`. The older black-box `recordsRequireProductionAnchor` gate remains conservative for the ordinary reference model.

The model is abstract, not an `io` patch. `io` is the first mixed-suite proof: its project now builds with one `io_package.Writer` identity, `export_test.go` lives in `io_internal_test_package`, external references bind the bridge by Go object identity, and test-owned adapters coexist with imported `io` adapters. Focused guards cover model selection, conditional internal-test detection, host targeting, mutation fallback, emitted-pair adapter ownership, alias-shadow stability, and collision-renamed embedded-value promotion.
**What the bridge owes the compiler once production is a REFERENCE.** A 62-package regeneration sweep found five defects that share one cause: the bridge is the same GO package as production, so every same-package test in the converter reads a production declaration as LOCAL — while its C# now lives in a closed referenced assembly. Each was fixed at the layer that made the wrong assumption.

- **A bridge member HIDES the production `using static`.** The bridge binds production through `using static <pkg>_package`, and C# member lookup stops at the first enclosing type carrying the name — so any member the bridge declares hides *every* same-named production member, overload resolution included. container/heap's `func (h *myHeap) Pop() any` hid `heap_package.Pop(Interface)`, and the suite's own `Pop(h)`/`Push(h, i)` bound the extension by value (CS1620 ×8). Such a reference is emitted production-class-qualified (`go.container.heap_package.Pop(…)`), the remedy `packageBuiltinShadows` already applies to a shadowed `using static go.builtin`. The shadow set is the internal variant's package-level declarations **plus its methods** — those emit as static extension members of the same class — keyed on raw Go names, and consulted only for a production-declared package-level object.
- **A production type is FOREIGN for a VALUE implement.** "Local" is what selects go2cs-gen's partial-struct realization, which folds the interface into the type's own declaration; a closed referenced type instead gets a per-interface `ᴠ` VALUE ADAPTER. Both consequences matter and only together: the cast site must CONSTRUCT the adapter, and the record must be EXEMPT from the interface-inheritance prune (sound only for the one-type-one-interface-list partial shape). encoding/binary's `TestByteOrder` casts `BigEndian` to a function-local `byteOrder` that EMBEDS the production `ByteOrder`, so the subsumed `bigEndian → ByteOrder` pair was pruned as covered — true while a merged partial carried it, false against a referenced assembly, and every `Read(r, BigEndian, data)` was CS1503. The arm mirrors the existing both-foreign one, `importedValueImplements` check included, so a pair production already records stays a bare implicit conversion.
- **A value adapter must name the anchor its record lands in.** A mixed white-box suite has two metadata anchors, and the value-adapter reference qualified through the external test class unconditionally — right for a production↔production pair, wrong for one whose BRIDGE-declared interface anchors it at the bridge (CS0426). The reference now applies `splitWhiteboxVariantRecords`' own bridge-declared-name predicate to the record's two participants, folding in the live LIFTED claims while the bridge is the variant under conversion. (The pointer form already reached the same answer through its deferred marker's `emittedAdapterPairAnchors`; a value adapter's name is composed inline and has no marker to resolve.)
- **A bridge-declared LIFTED name must reach the record split.** That split keys on the record's EMITTED name against a set built from go/types `TypeName` defs, which carry the GO-SOURCE name — so encoding/hex's `type r struct{ io.Reader }` inside `TestEncoderDecoder` was collected as `r` while its record named `TestEncoderDecoder_r`. The record anchored in the external class and the generator declared a PHANTOM empty type there; every symptom followed from that one type being empty (CS0103 on the promoted embed it does not declare, CS0034 because a phantom carries no `[GoType]` and so no TypeGenerator `(T,T)` `==` to bind exactly, CS1503 at the cast site). The internal variant's live lift claims are unioned in right after it converts, while they still stand.
- **A BARE record name resolves in the variant that RECORDED it, never across variants.** The bridge's declared-name set is a set of SIMPLE names, and the two `-tests` variants are separate Go packages, free to declare the same one: encoding/gob declares `Point` in `codec_test.go` (`package gob`) and again in `example_interface_test.go` (`package gob_test` — the one whose `Hypotenuse` implements `Pythagoras`). Each variant's records are split as that variant converts, and every cross-variant reference is routed by `go/types.Object` identity to a CLASS-QUALIFIED spelling — `whiteboxBridgeNamedType` renders an internal-test type the external suite names as `global::<ns>.<pkg>_internal_test_package.T`, and `whiteboxProductionObject` does the mirror while the bridge converts — so a bare name recorded by the external suite is external-declared *by construction*, whatever the bridge spells the same way. Matching it against the bridge's set regardless anchored the EXTERNAL pair `Point → Pythagoras` in `package_info_internal_test.cs`, where `Pythagoras` is not in scope: `CS0246`, no test host built, and all 106 of gob's verdicts read empty — a missing host masquerading as mass runtime failure. The set is therefore consulted only while splitting the BRIDGE variant's own records (`splitWhiteboxVariantRecords`' `bridgeVariant`), and the emission mirror that names an adapter through the anchor its record will land in carries the identical gate (`whiteboxBridgeDeclaredType`), so the two cannot disagree about a pair. Write-time qualification is not a substitute: `qualifyAmbiguousTestTypeRefs` roots an ambiguous bare name at the file it is ALREADY being written into, so a mis-anchored record comes out merely qualified to the wrong variant's class (`gob_internal_test_package.Point`). Guarded by `TestSplitWhiteboxVariantRecordsResolvesBareNamesInTheRecordingVariant`, over a fixture module that declares `Point` in both variants and asserts the collision itself through the real `go/types` scan (`collectWhiteboxBridgeTypeNames`) before exercising either split.
- **The friend bridge's box receivers survive into the pointer adapter.** sync's `export_test.go` declares `PushHead`/`PopTail` on the production `*poolDequeue`/`*poolChain`; the converter emits them as direct-ж primaries, so `poolDequeueжPoolDequeue` must forward `m_box.PushHead(val)`. Two generator defects stacked: the bridge scan matched the receiver's parameter TEXT against one composed spelling (`ж<poolDequeue>`) when the bridge qualifies it however its own file needs — `this ж<global::go.sync_package.poolChain>` once a `go/*` package in the closure shadows the root namespace — and the foreign-struct arm then re-derived every member's receiver from the referenced assembly's METADATA, where a bridge-contributed method does not exist, clobbering the binding back to `m_box.Value` (CS1929 ×5). The scan is now on the ж argument's last dotted segment (well-defined because it runs only where the struct has no local declaration), and a bridge-bound member is left alone.

A sixth, in `golib`, was masked behind those compile failures: **`reflect` reads a package class's `[GoPackage]` stamp, not its NAME.** `GoReflect` reconstructed a type's Go package by trimming `_package` off the declaring class name — a heuristic the bridge breaks by design, since `binary_internal_test_package` hosts declarations Go-declared in `package binary`. Both readers (`GoTypeName`'s qualifier and `PkgPath`) now prefer the stamp, with the name-trim kept only for a hand-written class carrying none; the two agree for every ordinary converted package. encoding/binary catches it through Go's own asserts twice: `TestNoFixedSize` compares the error text `… not fixed-sized in type *binary.Person`, and `TestSizeAllocs` NAMES its subtests from `reflect.TypeOf(v)`, so a whole subtest set appeared under invented names with no Go counterpart.

**Reference closure (the declaration-edge rule).** The test project's references are the direct-import set, plus the alias scan (B2c), plus — because binding *any* referenced type in C# requires the assemblies that type's **own declaration** names — the **declaration closure** of that set (`declarationClosureImports`, both project models). Two declaration edges carry it: an interface's **base interfaces**, and a struct's **field types**. *Interface bases:* Go interfaces satisfy structurally *and* compose by embedding; C# interfaces are nominal, so the converter carries both shapes as C# inheritance at each interface declaration (`getStructuralInterfaceBases`): `hash.Hash` **embeds** io.Writer, io/fs's `fs.File` lists `Read`/`Close` explicitly and does *not* embed io, and both emit a converted declaration that NAMES an io base. Such a base edge belongs to the **declaring package's** import graph, so it appears in no test import and no alias `using`; `DisableTransitiveProjectReferences` (B2b) then hides the declaring assembly's own io reference, and every site that names the interface fails CS0012:

- the emitted **conversion record** — hash/maphash's `[assembly: GoImplement<Hash, hash_package.Hash64>(Pointer = true)]` and crypto/hmac's `GoImplement<hmac, hash_package.Hash>`, whose closures reach `hash` but never `io` (`'io_package.Writer' is defined in an assembly that is not referenced`);
- the **go2cs-gen adapter** realizing that record, whose class declaration lists the interface; and
- every converted production/test **source** that names it (`(fs.File, error) Open(...)`, hmac's `justHash`).

*Struct fields at a composite literal:* the converter renders a Go composite literal as `new T(Field: …)` — a call to the **fieldwise constructor** go2cs-gen generates for a `[GoType]` struct, whose parameter list spells out **every** field's type — so binding that call needs every field type's assembly. `testing/quick`'s `Config` holds a `Rand *rand.Rand`, so `image/draw`'s `quick.CheckEqual(orig, sqDiff, &quick.Config{MaxCountScale: 10})` fails `CS0012: The type 'rand_package.Rand' is defined in an assembly that is not referenced` *at the `new quick.Config(…)` expression*, with `math/rand` in no import list on either side. No interface closure can ever reach it — `Rand` is a **struct**, so the shape is invisible to a base-interface walk; it is the same missing-declaration-edge defect one type-kind over.

The alias scan cannot reach either class: the named type *does* bind by name — its own package **is** referenced — and what is missing is a package named inside that type's own C# declaration. Three gates keep the closure minimal, because over-inclusion is its own defect — every extra reference is churn across the banked corpus plus a chance at a duplicate-type conflict:

- **Seed only from the files the test assembly COMPILES** (`referencedTypeSeeds` walks the retained syntax rather than iterating the `TypesInfo` maps, which also makes seed order deterministic). A Phase-4D compile-excluded Example/Benchmark-only file is analyzed — so its declarations still reach the manifest — but no C# is emitted for it, so it names nothing the compilation must bind. Seeding from one handed `compress/gzip` five references (`context`, `crypto/tls`, `mime/multipart`, `net/http`, `net/url`) reached through `http.Request`'s fields, from an `example_test.go` that is not compiled at all, and handed `go/token` a `go/ast` reference the same way.
- **Start the interface walk from named types, never from whole packages,** and follow bases **transitively** (`b.B : a.A : io.Writer` needs both `a` and `io`, because a base's own declaration must bind in turn). C# needs a base's assembly only when the derived interface is BOUND, and walking every exported interface of every referenced package would hand `io` to nearly the whole corpus through `fmt.State`'s structural io.Writer base — a reference no project that merely calls `fmt.Sprintf` requires.
- **Fire the struct-field edge only where a composite literal CONSTRUCTS the struct** — that, not mere value use, is what demands the field types, and one level suffices (the generated constructor's parameters default unless supplied, and a *nested* literal is itself a seed). Measured against the corpus: eleven banked packages hold `sync.Once`, `sync.Map` and `reflect.Value` **values** (strconv's package-level `atofOnce`, encoding/binary's `reflect.ValueOf`) and compile clean today with no reference to `sync/atomic` or `internal/abi` — so a "named by value" rule would add eleven references that nothing needs. `os.File`'s single `*file` field likewise never drags internal/poll, syscall and the rest of os's private graph in. An **EMPTY** literal carries the edge only for a struct declared in a **ROOT** package, and the boundary is ACCESSIBILITY rather than a package list: `T{}` renders `new T(nil)` — go2cs-gen's dedicated nil constructor, which names no field — but the FIELDWISE overload stays a resolution candidate wherever it is visible, and binding a candidate means binding its parameter types. That constructor is `internal` for any struct with an unexported field, so outside its assembly and friends it is not a candidate at all (which is exactly why mime's `once = sync.Once{}` and testing/quick's `return reflect.Value{}, false` need nothing), while a root package's struct IS visible that way — recompiled into the test assembly, or reached through the white-box `InternalsVisibleTo` grant. math/rand/v2's `*p = ChaCha8{}` failed `CS0012 … 'chacha8rand_package.State' … assembly that is not referenced` at the `new ChaCha8(nil)` expression, with internal/chacha8rand named in no import list on either side.

A **root's own package is never an addition**, and the external variant makes that load-bearing rather than theoretical: `go/packages` names it `<pkg>_test`, which resolves to no importable package at all, so a `bytes_test` struct literal whose field type is declared beside it fails the whole conversion with F14b's loud `resolve test project dependency "bytes_test": package bytes_test is not in std`. Every root's `PkgPath` therefore seeds the already-referenced set (its types compile into the test assembly, or bind through the production project reference the template already carries).

`testing` is skipped as a walk **source** (`closureWalkable`): it binds to the hand-owned `core/testing` shim per F15b, whose C# declarations are authored by hand and share only *names* with Go's — Go's `testing.T` embeds a `common` holding `io.Writer`, `time.Time`, `sync.RWMutex` and a dozen more, none of which the shim's two-field `T` declares, so inferring C# edges from the Go declaration there is simply invalid. Every `-tests` compilation names `testing.T`, making this the widest over-inclusion the struct rule could possibly cause; nothing is lost, since the shim's reference is fixed in the project template.

Each interface step runs the same `types.Implements` candidate match the converter uses at the declaration site (identical exported / non-alias / non-generic / method-set / strictly-fewer-methods gates) — deliberately taken *before* that function's covered-by-embed skip and minimal-covering-set prune, so the result is a superset of the emitted base list and no emitted base's assembly can be missing. Only the declaring package's own imports are scanned, so a same-package base needs no separate visit (an interface implements its base's bases too, so those candidates are found directly), and the output is a sorted set, so the map-ordered walk stays deterministic. It is a pure project-reference concern (the manifest dependency list stays import-derived), empty for a package whose named types carry no foreign declaration edge (unicode/utf8/path/cmp/itoa → unchanged), and `{io}` for io/fs, hash/maphash and crypto/hmac. **Measured minimality:** regenerating every banked package's `.tests.csproj` and diffing is the instrument, and it is what rejected each looser rule in turn — an un-file-scoped seed drifted `compress/gzip` (context, crypto/tls, mime/multipart, net/http, net/url) and `go/token` (go/ast); a "named by value" struct edge drifted eleven more (encoding/binary, errors, hash/crc64, internal/fmtsort, math/rand, math/rand/v2, mime, os/signal, strconv, strings, testing/quick); an "any composite literal" edge — the empty form *unscoped* — still drifted three (encoding/binary, mime, testing/quick). Every one of those gates remains a **zero-drift** rule. The **root-scoped** empty-literal edge is measured the same way, and it is the one edge that is deliberately not zero: at the 63-package roster (2026-07-31), converting every package twice on one binary with that edge neutered and restored, it changes exactly **one** project — `math/rand/v2`, by exactly **one** line, `<ProjectReference Include="$(go2csPath)core/internal/chacha8rand/internal.chacha8rand.csproj" />`. That is the root set itself and nothing else: the three foreign-struct negatives (`mime`, `testing/quick`, `encoding/binary`) that rejected the unscoped form stay byte-identical, as does every other banked package. (Run that probe with the converter's exit status checked — 63/63 conversions must succeed on **both** sides: a conversion that *fails* writes no csproj, so an ignored failure reads exactly like "no drift", and that false-clean is how the `<pkg>_test` self-reference below first hid.) Guarded by the `TestSelectTestProjectModel`, `TestRecordsRequireProductionAnchorGatesReferenceModel`, `TestWriteTestProjectReferenceModelBindsProductionProject`, `TestReferenceModelSeedAnchorsTestClassOnly`, and `TestDeclarationClosureImportsSurfacesForeignDeclarationEdges` (foreign-package interface base, own-package structural base, transitive `b → a → io`, the `fmt.State` narrowing with its positive control, the empty-closure case, the `*rand.Rand` struct field at a composite literal **with its by-value-only negative**, a func-typed field's signature, the one-level field boundary, the compile-excluded-file negative with its own positive control, the external-variant self-reference negative, and the `testing`-is-never-walked negative) converter unit tests; unicode (28 tests, incl. `TestSpecialCaseNoMapping` and the `testing.Benchmark`-driven `TestCalibrate`) is the first package it validated under the reference model, and crypto/hmac (172 tests) the first the generalized foreign-package rule unblocked. (io/fs compiles clean under this closure but does not yet *validate*: its `TestCVE202230630` globs a 10 001-separator pattern whose faithful `globWithLimit` recursion overflows .NET's fixed thread stack before Go's `depth > 10000` guard fires — a Go-growable-stack vs .NET-fixed-stack divergence in the test host, orthogonal to the reference closure.)

**The third closure edge — a MEMBER ACCESS (2026-08-03, r38-gob).** The two edges above are edges of a named
TYPE's declaration; the third is the edge of an **access**: resolving `x.M` requires BINDING `x`'s type, and
when `x` is declared in another package that type is spelled nowhere in this compilation — not in an import,
not in an alias `using`. `unique` is the witness: `handle.go` declares `var cleanupMu sync.Mutex`, the
white-box suite calls `cleanupMu.Lock()`, and the test project referenced no `sync` — `handle_test.cs` failed
`CS0012 … 'sync_package.Mutex'` twice, no host linked, and the package's suite had never been measured. Adding
the production package's whole import list instead is the looser rule the reference model exists to avoid (it
would have added `internal/stringslite` and `unsafe` here, and far more elsewhere). The seed is every
`*ast.SelectorExpr`'s BASE type, `namedTypesIn`-expanded, then `reach()`ed and `enqueue()`d like a field edge;
a package-QUALIFIED selector (`sync.Mutex`, `lib.F`) is not this shape at all — its base is a `PkgName`, which
has no type — and the import that spells it already carries the reference.

**Measured minimality — two restrictions, each of which the roster REJECTED a looser form of.** The probe is
the one this section prescribes (regenerate every banked package's `.tests.csproj` and diff, with the
converter's exit status checked on both sides: 73/73 conversions must succeed, since a failed conversion
writes no csproj and reads exactly like "no drift"). (1) *The receiver, not every named declaration.* "The
type of every var/const/func the compilation NAMES" is equally true of C#'s binding rules in the abstract, and
drifts **23 of 73** — `bufio` into compress/bzip2 and image/gif, `internal/abi` + `internal/reflectlite` into
errors, three references into hash/crc32 — all of which compile clean today with none of it. Naming a
declaration does not force its signature to be materialized; accessing a member of it forces the receiver's.
(2) *`_test.go` files only, scoped per FILE.* Under the reference model the production sources are not in this
compilation — they are in the referenced assembly, which carries its own references — and seeding from them
too still drifts **13** (crc32's `castagnoliOnce.Do`, math's `cpu.X86`, …). Per-file rather than per-package
because go/packages loads the INTERNAL test variant with the production files alongside its own, so a
package-level gate lets every production receiver back in. With both restrictions the roster is **zero-drift**:
unique's single `<ProjectReference Include="$(go2csPath)core/sync/sync.csproj" />` is the only line that
changes across all 73. Under the **recompile** model the edge is a no-op by construction — that model adds
`production.Imports` wholesale, so a production receiver can never be an addition. Guarded by
`TestDeclarationClosureImportsSurfacesMemberAccessEdges` (the unique shape through real test variants, plus
the named-but-not-accessed, production-source-only and package-qualified negatives). One more rule fell out
of the family's own `testing`-never-walked negative: `testing` must never be an ADDITION either, for the same
reason it is never a walk SOURCE — its reference is fixed in the project template, which is why the caller
strips `"testing"` from the import-derived set rather than passing it through as already-referenced, so
`reach()` now honours `closureWalkable` too.

**The fourth and fifth closure edges — a ZERO-VALUE DECLARATION and a RECORDED INTERFACE BASE (2026-08-07, r43f).**
`log` and `go/scanner` sat on the build-blocked list with one CS0012 family each, and each named a demand the
first three edges structurally cannot see.

*`log` — the zero-value declaration.* `TestNonNewLogger` writes `var l Logger`. There is no composite literal
anywhere in it, so the struct-field edge's `*ast.CompositeLit` seed never fires — but Go's zero value of a
struct is not `default(T)` in the emission. The converter renders the declaration as a **constructor call**:
`ref var l = ref heap(new Logger(), out var Ꮡl)` for the address-taken shape (`escapeAnalysisOperations`),
`new Logger()` otherwise (`visitValueSpec`). C# overload resolution must materialize every **accessible**
constructor's signature before it can choose one, and under the white-box `InternalsVisibleTo` grant the
package-under-test's `internal` fieldwise overload is accessible — so `log_test.cs` failed
`CS0012 … 'atomic_package.Pointer<>' … assembly 'sync.atomic'` at `new Logger()`, `Logger`'s three `atomic.*`
fields being spelled in no import list on either side. This is the **empty-literal edge's exact demand by
another route**, so it feeds the same seed (`constructedEmpty`) under the same ROOT/accessibility gate, scoped
to `_test.go` files for the member-access edge's reason. `math/rand/v2`'s `*p = ChaCha8{}` and log's
`var l Logger` are now one rule.

*`go/scanner` — the recorded interface base.* A converted **concrete** type names no interface in its own
emitted declaration — `[GoType("[]ж<ΔError>")] partial struct ErrorList;` names none at all. Its bases arrive
as the **VALUE-form `[assembly: GoImplement<T, I>]` records** its package emits, which the go2cs-gen
`ImplementGenerator` realizes as `partial struct ErrorList : global::go.sort_package.Interface` **inside the
declaring assembly**. The metadata type therefore *declares* that base, and binding **any** member on it makes
the compiler resolve the base list: `list.Sort()`, `len(list)`, `Ꮡlist.RemoveMultiples()` and the generated
`ErrorList`→`error` value adapter's own `m_value.Equals(…)` all failed
`CS0012 … 'sort_package.Interface'`, thirteen times across the suite, with `sort` in no test import and no
alias `using`. The edge hangs off the **member-access seed** the third edge already computes — it is what
binding that receiver additionally costs — and interfaces are excluded from it because the base walk covers
them already.

**Measured minimality — and here the instrument rejected the rule C#'s binding rules appear to justify.**
"The interfaces the receiver's type implements, taken from the declaring package's imports" is the natural
`go/types` statement of the edge, mirroring `interfaceBaseCandidates` one type-kind over. It drifts **16 of
the 96** banked projects. The reason is that a `GoImplement` record exists only where the converter converted
a **cast**, so Go satisfaction wildly over-approximates the emitted base list: `os.File` satisfies
`syscall.Conn` and handed `syscall` to thirteen projects (compress/{flate,gzip,lzw,zlib}, image and four
image codecs, io, math/rand/v2, regexp, strconv) — while `os` records `File` only against `io/fs.File` and
`io.Writer`, and both **POINTER-form**, which generates an adapter *class* (`FileжWriter`) rather than a base
on the type and so demands nothing of a member binding; `bytes.Buffer` satisfies most of `io` and handed `io`
to `sort` and `unicode/utf8` though `bytes` emits **no records at all**; `internal/buildcfg`'s `Stringer`
handed it `fmt` from an equally empty set. All sixteen compile clean today with none of it. So the **records
are the gate** and satisfaction merely supplies the candidate universe: `packageImplementBases` parses the
package's own freshly-written `package_info.cs` (value form only — `parseExportedValueImplementLines` already
drops the pointer form), keys the interfaces by the implementing type, and the walk fires only where the
receiver's type *and* the candidate's package both match a record. Keyed **per type**, because os's one
genuine `syscall` record is for `rawConn`, not `File`. With that gate the roster is **zero-drift**: converting
all 96 banked packages on both binaries changes not one line of one `.tests.csproj`. The lookup is scoped to
the package under test, whose `package_info.cs` this run has just written; a foreign type's base list is its
own package's record set in its own `package_info.cs`, and no measured case has ever demanded one — widening
is the same lookup pointed at that file.

*`go/types` — the widening, measured.* That last sentence held until `go/types`' own suite, whose host build
produced exactly one error: `check_test.cs(78,66): CS0012 … 'sort_package.Interface'`, at `len(list)` in
`if list, _ := err.(scanner.ErrorList); len(list) > 0`. The type is `go/scanner`'s `ErrorList` — the very
type the same-package edge was built for, one package over. `go/scanner` **is** referenced (the suite imports
it) so `ErrorList` binds; what does not resolve is the base list `go/scanner`'s **own** record set realizes on
it, and `sort` sits in `go/types`' PRODUCTION project references (the package imports it) and in no test
import. So the records are now read **per declaring package**: a root type's from the `package_info.cs` this
run just wrote, a foreign type's from that package's, resolved through the same `getImportPackageInfo` route
the `<ImportedTypeAliases>` block already reads a dependency's metadata by (layout L3's per-GOOS placement
included) and memoized per import path. An unreadable or absent file yields no edge, exactly as it does for
the package under test. The gate itself is untouched — same value-form records, same per-type key, same
candidate match — which is why the widening does not reopen the 16-of-96 question: it points one lookup at a
different file.

*The other half: the member bindings that spell no selector.* The same measurement moved the edge's SEED as
well. `check_test.go` never writes `list.Sort()` — it binds `ErrorList` only through `len(list)` and
`range list` — so the member-access seed (`x.M`, the third edge's) never saw it. Each of those lowers to a
member on the value's type all the same: golib's generic `len`, the emitted enumeration, an indexer. The seed
therefore also carries the types a compiled `_test.go` binds a member on through a **builtin call, a range, or
an index/slice** (`memberBound`), and that set feeds the implemented-interface edge **only** — never
`reach`/`enqueue`, because the value is spelled by a package the suite already imports. The boundary the
member-access edge measured stays exactly where it was, and is pinned by the same negatives: a test that
merely NAMES a value of such a type, or PASSES IT ALONG to a function with an exact parameter type
(`var r Rows; Order(r)`), binds nothing on it and surfaces nothing. Seeding this edge from every named type
instead would cross that line — the existing negative fails immediately — which is why the shape is the
member binding rather than the mention. `go/types`' reference set gains exactly one line, `core/sort`, and the
host builds clean. (Guarded by `TestDeclarationClosureImportsSurfacesForeignImplementedBases` — the foreign
positive plus four negatives (no resolver, empty record set, a record keyed to another type, and the
name-and-pass boundary) and the range/index forms — and by
`TestForeignImplementBasesResolverReadsDeclaringPackageInfo`, which pins the value-form-only parse, the
memoization, and the silent-nothing on an absent file. Both fail against the pre-fix converter, and each half
of the fix fails it independently.)

Guarded by the `TestDeclarationClosureImportsSurfacesZeroValueVarDeclarations` (the log shape through real
test variants, plus the foreign-struct accessibility negative and the production-source scoping negative) and
`TestDeclarationClosureImportsSurfacesImplementedInterfaceBases` (the scanner shape, plus the three negatives
that pin the gate: **no records ⇒ no edge** even where `types.Implements` says yes, a record keyed to another
type, and the receiver restriction) converter unit tests. `go/scanner` **validates 11/11** on the fix.
`log` builds and runs for the first time — seven of its nine test functions agree with `go test` — but does
**not** bank: two roots stand behind the closure one, `runtime.Caller`'s unimplemented `getcallersp` stub
(`TestAll`, the same row `testing/slogtest` carries) and `TestDiscard`'s exact allocation-count assert
(the established `alloc-profile` class). ⚠ The `Caller` root closed on 2026-08-07 and `log` **still** does
not bank — `TestAll` asserts the GO source file's own extension and line numbers, which the converted
program does not carry; see *`runtime.Caller` works by severing the FUNNEL* below.

### An Example/Benchmark-ONLY test file is dropped from the compile set (Phase-4D file exclusion)

`Example` and `Benchmark` declarations are uniformly **Phase-4D-deferred** — `discoverTestDeclarations` records them in the manifest with status `unsupported` ("… execution is deferred to Phase 4D") and the differential oracle filters them from both sides (`eligibleTerminalTestResults` admits only `included` `test`-kind declarations). The **option-a ruling** (2026-07-24) extends that deferral from the *declaration* to the *file*: a `_test.go` file is dropped from the `-tests` conversion/compile set (`selectCompileExcludedTestFiles`) iff **both**

1. **every top-level declaration it contributes is a Phase-4D-deferred `func Example*`/`func Benchmark*`** — imports do not count as declarations; any top-level `var`/`const`/`type`, or any other func (a `Test`/`TestMain`/`Fuzz` func, a method, or a *mis-signatured* Example/Benchmark), disqualifies the whole file (conservative by design; `TestMain`/`Fuzz` are deliberately out of scope). The classification is the **exact** `isPhase4DExcludedTestFunc` predicate `discoverTestDeclarations` uses (no receiver, no results, no type params, and either a zero-parameter `Example*` or a single-`*testing.B`-parameter `Benchmark*`), so a file qualifies only when it truly contributes nothing to the run registry; **and**
2. **no RETAINED test file references any object the file declares**, resolved by go/types **object identity** across the loaded variant set (never filename or text) — a promotion **fixpoint** over both variants, so an Example a retained test takes by value (`[]func(){ExampleWired}`) keeps its file compiled, and a candidate promoted back to retained can in turn pull further candidates in.

The predicate is pure `go/ast`+`go/types`: go/token's `example_test.go` declares **only** `func Example_retrievePositionInfo()` at top level (its `type p = token.Pos` / `const bad` / `func ok` live inside a raw-string literal fed to `parser.ParseFile`), so a text scan would wrongly disqualify it while the AST predicate correctly qualifies it. Excluding it is the **demonstrated consumer**: that external `package token_test` file, recompiled into go/token's mixed whitebox+blackbox test assembly, names `token.FileSet`/`ΔPos`/`Token` — types the referenced `go.parser`/`go.ast` assemblies surface from the *production* `go.token` assembly while the recompile makes a *second*, local copy — CS0012. With the file excluded, go/token compiles.

**Metadata representation — discovery stays intact; only emission + compile-membership are dropped.** The excluded file's Example/Benchmark declarations still appear in the manifest under their existing disclosed-unsupported status, and its source is listed in `testSources` with a distinct `example-benchmark-only` status. This is *required* for oracle exactness, not cosmetic: `go test` **runs** an Example that carries an `// Output:` comment (go/token's does), so it appears in the raw `go test -json` stream, and the **F6 census gate** (`manifestCensusGaps`, computed over the *unfiltered* Go results) fails the comparison for any run declaration the manifest does not account for. Dropping the declaration would trip the census; keeping discovery — and dropping only the file's `.cs` emission and its `.tests.csproj` compile item — keeps every already-filtered Example filtered and the differential oracle exact. The predicate lives in `convertTestVariants` (both project models honor it): discovery runs over every test file, emission over the non-excluded subset.

**Blast radius (banked packages).** Because the policy changes which files a *banked* package compiles, a GOROOT scan of every committed test suite (with go/token as the positive control) found **18** packages with a qualifying file — each re-validated with identical `Test` counts and rebanked to remove the excluded `*_test.cs`, its `.tests.csproj` compile item, the project references + `using` aliases the excluded file **exclusively** pulled in (verified by import analysis, e.g. math/cmplx's fmt, encoding/hex's os/io.fs/log), and — where the excluded external file was the sole contributor to the external anchor — the orphaned `package_info_external_test.cs`. One subtlety is guarded operationally: the metadata writer **merges** with the committed anchor, so a `GoImplement`/`GoImplicitConv` record contributed *only* by the excluded file (math/rand's `GoImplement<text.tabwriter.Writer, io.Writer>`, from an Example that casts a `*tabwriter.Writer`) survives the merge as a stale record referencing a now-unreferenced assembly (CS0234); regenerating the anchor from a clean state (as a whole-corpus reconvert would) drops it. The 17 packages with no qualifying file stay byte-identical.

Guarded by the `TestSelectCompileExcludedTestFilesDropsExampleAndBenchmarkOnly` (positive: external Example-only + internal Benchmark-only), `TestSelectCompileExcludedTestFilesKeepsExampleWithTopLevelVar` (condition 1 negative), `TestSelectCompileExcludedTestFilesKeepsReferencedExample` (condition 2 fixpoint), and `TestSelectCompileExcludedTestFilesKeepsTestMainAndFuzzOnly` converter unit tests.

### A package-qualifier `using` in a converted TEST SOURCE contributes a project reference

Test projects set `DisableTransitiveProjectReferences=true`, so an assembly the package reaches only *transitively* is invisible to the test compile (CS0234). `aliasReferenceImports` covers this by scanning `using` ALIASES for namespace tokens of packages in the transitive import closure and adding a direct project reference for each. It formerly scanned only the two **metadata** files, despite its contract covering a file-local package-qualifier `using`; it now scans the converted `*_test.cs` **outputs** as well.

The shape this misses otherwise has no textual import at all: math/rand's `default_test.go` does not import `os/exec`, but `testenv.Command(…)` **returns** `*exec.Cmd`, so the emitted `default_test.cs` binds `cmd.Value.Env` and `cmd.CombinedOutput()` through the os/exec assembly while `exec.` never appears in any import list. Scanning the emitted source finds the qualifier `using` and emits `$(go2csPath)core/os/exec/os.exec.csproj`. The manifest's dependency list stays import-derived — alias targets are purely a project-reference concern — and the scan is additive, so a package whose sources introduce no new qualifier is unchanged.

### Shadowing the names go2cs itself spells (`nil`, golib names, emitter-spelled type names, C# keywords)

A census (2026-07-16) of every non-function predeclared Go identifier, the golib public top-level type surface, and the C# keyword list — checked against the three name-protection mechanisms (`keywords` `@`-escape, `reserved` `Δ`-rename, and the shadow analyses) with a minimal transpile-and-run repro per candidate — found five real gaps, each fixed and guarded by the `ReservedNameShadows` behavioral test:

- **A local named `nil`** (`nil := 5`, legal Go) was emitted as the nil-literal rendering — `nint default! = 5;`, a syntax error — because the ident conversion matched on the *name*. It now checks the resolved object (mirroring the `true`/`false` handling): only a use resolving to the universe `*types.Nil` renders as the literal (`default!`, or golib `nil` in pointer contexts); a shadowing object falls through to normal rendering (`nint nil = 5;` — `nil` is not a C# keyword, and Go's own scoping guarantees no nil-literal use while shadowed).
- **User types named `builtin` or `sstring`** shadowed golib names the emitter references even when the Go source never spells them — the qualified `builtin.len(…)` calls (emitted when a package method shadows a built-in) bound the nested user struct (CS1501), and the string([]byte) elision's `sstring` views bound the user type (CS0030). Both names joined the `reserved` set: the user types decline to `Δbuiltin`/`Δsstring`.
- **User types named `any`, `rune`, `nint`, or `nuint`** shadow spellings the *emitter itself* produces: `interface{}` renders `any` (`slice<any>` bound the user struct, CS0029), an untyped rune-constant default spells `rune` (`c := 'x'` emits `rune c = 'x';`, CS0030), and Go `int`/`uint` map to the C# native-int contextual keywords (`partial struct nint { internal nint d; }` is a CS0523 layout cycle, and `@` cannot fix a name-identity problem). These names must **never** enter the string-based `reserved` set — legitimate emissions re-enter the same sanitizers, so corpus-wide `slice<rune>(…)` would corrupt to `slice<Δrune>` and re-fed delegate compositions corrupt `Func<…, nint, nint>` to `Δnint`. Instead `performNameCollisionAnalysis` registers a package-level TYPE bearing one of these names (`emitterSpelledTypeNames`) in the package-scoped `nameCollisions` map: every ident with that name *in that package* is `Δ`-renamed — exactly mirroring Go's package-scoped shadowing — with zero effect on any other package ([CNR](Glossary.md#cnr) byte-identical).
- **`required` and `scoped`** are C# 11 contextual keywords banned as *type* names (CS9029/CS9062, surfacing in both the converted declaration and the TypeGenerator's output). Both joined the `keywords` `@`-escape set like `file` (`partial struct @required` — the `@` escape is valid in every position and the generator carries it through). `record`, `partial`, and the other contextual keywords compile clean as type names on C# 13/net9 (verified empirically) and stay unescaped.
- The keyword set carried the typo **`__argslist`**, which covered nothing: a Go local named `__arglist` hit the real (undocumented) Roslyn keyword and failed to parse (CS1002). Corrected — the local now emits `nint @__arglist = 5;`.

Census rows verified fine with **no action needed** (each proven by a transpile-run-compare repro): locals named any predeclared identifier (`nil`, `iota`, `error`, `any`, `comparable`, `rune`, the numeric type names — Go-consistent shadowing carries over); user types named `error` or a predeclared type name in the common self-consistent cases; embedded predeclared fields (`struct{ float64; rune; any; int; string }` — the color-color form `internal rune rune;` compiles, with keyword-mapped embeds escaping only the field NAME: `internal nint @int;`); the golib `Defer`/`Recover` delegates (never spelled in emitted code — the defer machinery's lambda parameters are inferred); and a local named `heap` alongside heap-boxing machinery (`heap<nint>(out var Ꮡx)` is a *generic* invocation, which a non-generic local simple name cannot shadow). Known residuals, documented rather than fixed (unreachable under default flags in any constructed repro, or pathological): a user TYPE named a numeric alias name (`float64`, `int32`, `uintptr`, `complex64`, …) in a package where an emission would be *forced* to spell that predeclared name through inference; and `type string struct{}` (the golib `@string` spelling is escape-identical to the keyword-escaped user name).

### A parameter that shadows an imported package is renamed at its declaration too
A function parameter whose name equals an imported package the function references — crypto/rsa's `func emsaPSSEncode(…, hash hash.Hash)`, where `hash` shadows the `hash` package named in the signature type `hash.Hash` — is shadow-renamed by the variable analysis (`hash` → `hashΔ1`) so it does not bind the `using hash = hash_package;` alias. Every **usage** already rendered the renamed name (convIdent reads `v.varNames`), but the parameter **declaration** was emitted from the raw `param.Name()`, so the signature kept `hash.Hash hash` while its uses were `hashΔ1` — CS0103 at every use (40 sites in crypto/rsa, 27 in testing/quick's `rand`). The declaration now resolves through the same `v.varNames` map, so it matches the usages:

```go
func emsaPSSEncode(mHash []byte, emBits int, salt []byte, hash hash.Hash) { … hash.Size() … }
```
```csharp
internal static (…) emsaPSSEncode(slice<byte> mHash, nint emBits, slice<byte> salt, hash.Hash hashΔ1) { … hashΔ1.Size() … }
```

A non-shadowed parameter maps to its own raw name (no churn). (Guarded by the `PackageShadowParam` behavioral test.)

A shadow-renamed **pointer** parameter completes the same rule on two more paths. A `*T` parameter is deref-aliased as `ref var <value> = ref Ꮡ<raw>.Value`, so its box companion `Ꮡ<raw>` always keeps the **raw** Go name even when the value alias is shadow-renamed — `func decrypt(rand io.Reader, …)` where `rand` shadows the `math/rand`-style alias becomes `ref var randΔ1 = ref Ꮡrand.Value`. **(A)** An address-of or by-pointer pass of that parameter must therefore use the raw box name `Ꮡrand`, not `Ꮡ`+value-alias `ᏑrandΔ1` (which is not in scope, CS0103) — `boxBaseName` returns the raw name for a pointer *parameter* specifically (unlike an escaping shadow-renamed *local*, whose box *is* the shadow form `ᏑiΔ1`). **(B)** When a function has **both** a pointer parameter and a shadow-renamed value parameter, its signature is rebuilt through a separate `updatedSignature` path (not the `generateParametersSignature` path fixed above), which had kept emitting the value param's raw name — so `EncryptOAEP(hash.Hash hash, …)` diverged from its `hashΔ1` uses again. That path now resolves value-param names through `v.varNames` too, matching the primary fix. Together these cleared 50 errors (crypto/rsa 23 + testing/quick 27). (Guarded by the `PackageShadowPointerParam` behavioral test.)

### A declaration shadowing a BUILT-IN makes the call an ordinary call
Go permits shadowing a universe built-in at any scope, after which a call through that name is an
ordinary call to the declaration, **not** the built-in — math/big's own tests declare
`make := func(z *Int) *Int { … }` as a function-local and then call `make(test.z)`. The converter's
built-in handling is keyed on the identifier's **name**, so such a call was emitted with built-in
semantics. Every built-in arm is now gated on the identifier actually resolving to the universe
built-in (`identIsUniverseBuiltin` — go/types records a genuine built-in as a `*types.Builtin`
object; anything else is a shadowing declaration), and a shadowed call falls through to the ordinary
call path:

```go
make := func(n int) int { return n * 2 }
fmt.Println(make(21))
```
```csharp
var make = (nint n) => n * 2;
fmt.Println(make(21));            // was: fmt.Println(new nint()) — the argument dropped entirely
```

Seven built-ins had a name-keyed emission arm and so were affected: `make` (→ `new nint()`), `new`
(→ `@new<nint>()` — both drop the argument, CS1503/CS1929), `panic` (→ the *statement* `throw
panic(x)` in expression position, CS8115), `print`/`println` (a spurious variadic `interface{}`
cast), and `len`/`cap` **when the argument is a pointer-to-named-array** (a spurious `.Value` deref
from the auto-deref arm). `close`, `min`/`max` and `recover` already carried the `*types.Builtin`
check; `append`'s arm self-bails on a non-slice argument; the remaining built-ins (`copy`, `delete`,
`clear`, `complex`, `real`, `imag`) have no dedicated arm and already fell through. Two *analysis*
paths shared the hole and were closed the same way: `isTerminatingStmt` treated a shadowed
`panic(…)` as terminating (mis-deciding a switch case's `break`), and the capture-mode scan treated
a shadowed `recover(…)` as forcing the function's defer frame.

Note this is the **opposite** direction from `packageBuiltinShadows` (see *Type-vs-Method Name
Collisions*): there the call genuinely *is* the built-in and a same-named package method shadows the
C# `using static go.builtin`, so the call is emitted **qualified** as `builtin.<name>(…)`. Here the
call is not a built-in at all. (Guarded by the `BuiltinShadowLocal` behavioral test.)

### A local that shadows a PACKAGE name is not a package qualifier

Go lets a variable, parameter or receiver take the name of an imported package; from its declaration
onward the identifier denotes the variable, and the package is simply unreachable in that scope. The
standard library's own test code does this freely — `format_test.go` has both
`func checkTime(time Time, …)` and `time := Unix(0, 1233810057012345600)` inside `TestFormat`.

Every emission out of `convSelectorExpr` used to be passed through `getAliasedTypeName`, the
QUALIFIED-NAME resolver. That function reads its argument as `<package>.<member>` and rewrites either
half — a collision-renamed foreign member (`time.Second` → `time.ΔSecond`), a Δ-shadowed import
qualifier (`color.RGBA` → `Δcolor.RGBA`), or a type alias (`color.RGBA` → `colorꓸRGBA`). Applied to a
rendered *expression*, it fired on any base that merely **shared a name** with an imported package, so
one function produced three different wrong answers depending only on which rewrite matched the member:

```go
func checkTime(time Time, test *ParseTest, t *testing.T) {
    if time.Year() != 2010 { … }      // Year is not renamed
    if time.Month() != February { … } // Month is a renamed TYPE
    if time.Hour() != 21 { … }        // Hour is a renamed CONST
```

```csharp
Δtime.Year()      // the import alias — the variable vanished
timeꓸMonth()      // the type alias — a type used as a method
time.ΔHour()      // the const rename applied to the METHOD name
```

The resolver is now gated on the selector's base actually **denoting a package**, asked through
`go/types` (`selectorBaseIsPackage`, consulted by `aliasResolvedSelector`), so a shadowing binding is
excluded by construction rather than by name. A non-package base could never resolve through the alias
maps anyway — they are keyed `<package>.<member>` — so the gate states the property once instead of
per emission site. This cleared 33 errors across five codes (CS7036, CS1061, CS1955, CS8130, CS1501) in
`time`'s converted test suite alone. (Guarded by the `PackageNameShadowing` behavioral test, whose
`describe(time time.Time)` calls all three member kinds on the shadowing parameter.)

### A collision-renamed member keeps the file's RENAMED qualifier

A package that collision-renames an exported const or var publishes it with the `const:` marker, which
tells a consumer to keep the reference **qualified through the package** (`time.ΔSecond`) rather than
alias it to a type. That arm carried the qualifier through verbatim — the raw Go package name — while
the file's actual `using` may be Δ-renamed because a same-named child namespace is visible (see
*importAliasOperations.go*). Both halves have to move together:

```go
import (
    "time"
    _ "time/tzdata"   // puts the `go.time` CHILD NAMESPACE in the assembly
)
… time.Nanosecond …
```

```csharp
using Δtime = time_package;   // `time` alone would bind the go.time namespace
…
time.ΔNanosecond              // WRONG — CS0234, `go.time` has no ΔNanosecond
Δtime.ΔNanosecond             // emitted now
```

The qualifier is run through `importQualifier` when — and only when — it is a single segment, so an
already `_package`- or `global::`-qualified spelling is untouched.

### A DOT-imported collision-renamed member has no selector to carry the rename

`import . "time"` makes every exported member a bare identifier, which is what `time`'s external test
files use. A foreign **type** still resolves correctly in that form, because `foreignAliasedTypeName`
works from `go/types` rather than from the source spelling; a **const or var** had no equivalent, so
`Second`, `Minute`, `Hour`, `Nanosecond`, `UTC` and `Local` — every one Δ-renamed in `time` because a
`Time` method shares its name — emitted raw and bound nothing (CS0103 ×176 across five files).
`convIdent` now resolves such a reference through the same recorded `GoTypeAlias` entries the qualified
path uses (`dotImportedRenamedMember`), and emits the renamed member **bare**: a dot import renders as
`using static <pkg>_package`, which exposes it under exactly that name. Only `const:`-marked entries are
honored — a type entry resolves to a `pkgꓸName` global-using alias, which is the type layer's business.

## Multi-Result Values and Comma-Ok Forms
Many Go functions return either a single value or a "value, ok"/"value, error" tuple, where only the declared return arity selects the behavior. You cannot differentiate C# overloads by return type alone, so the runtime types expose a second overload distinguished by an extra discard argument. For map access, the "comma-ok" read routes through a two-value indexer using the discard sentinel `ꟷ`:

```csharp
var v1 = m["Answer"];            // single value: zero value if the key is absent
var (v2, ok) = m["Answer", ꟷ];   // comma-ok: (value, present?)
```

These two forms can behave differently — case in point, [type assertions](https://golang.org/ref/spec#Type_assertions): the single-value form panics on failure, while the comma-ok form returns safely with a boolean success result. Type assertions convert similarly, through a generated `_<T>()` accessor:

```csharp
var t = i._<MyType>();              // panics on failure
var (t, ok) = i._<MyType>(ᐧ);       // comma-ok, safe
```

The asserted type is a **type position**, so an assertion to a **pointer** type renders the pointer *type* `ж<T>`, not a value dereference: `i.(*box)` → `i._<ж<box>>()`. (The starred-operand-is-a-type case previously emitted the type form only inside a `(*T)(p)` cast; a non-parenthesized `*type` fell through to the value-deref path and emitted `box.Value` — CS0426, since `Value` is not a member type of `box`.) (Guarded by the `TypeAssert` behavioral test's `*box` assertion; runtime hit this in `netpoll.go`'s `arg.(*pollDesc)`.)

**An assertion to a NAMED interface must record the concrete implementation, even from a non-empty interface source.** At run time `_<T>()` resolves a NAMED target interface *only* through a compile-time `GoImplement` adapter (golib `TryTypeAssert`'s structural duck-typing path exists only for anonymous interfaces — a named interface with no generated `ᴛAs` method is treated as a miss, not converted). So `h.(encoding.BinaryMarshaler)` — where `h` is a `hash.Hash32` whose dynamic type is `*digest` — needs `[assembly: GoImplement<digest, encoding.BinaryMarshaler>(Pointer = true)]` or it panics at run time (`interface conversion: … not encoding.BinaryMarshaler`), *after compiling cleanly*. The converter recorded such implementations only for an **empty**-interface source; a non-empty interface source (the common `hash.Hash32`/`sort.Interface`/… case) recorded nothing, because `getUnderlyingType` on an interface-typed expression yields the interface, not the concrete dynamic type. `convTypeAssertExpr` now, for a non-empty interface source asserting to a **named** target interface, enumerates the package's concrete types that implement **both** the source and target interfaces — exactly the dynamic types the assertion can succeed on — and records a `GoImplement` for each (probing the value then the pointer form, so a pointer-receiver `MarshalBinary` records against `*digest`). The both-interface filter keeps the set tight (adler32 → just `digest`); an **anonymous** target interface is excluded (it resolves through its `ᴛAs` method and needs no adapter, so recording one would be dead machinery). Known limitation: only the current package's scope is scanned, so an external `p_test` assertion whose concrete type lives in the package under test, or a dynamic type imported from a third package, is not yet covered. This unblocked the `hash/*` marshal round-trips (`TestGoldenMarshal`). (Guarded by the `InterfaceToInterfaceAssertion` behavioral test — a `Stringish`-typed value asserted to a named `Marshaler` it implements, plus a comma-ok miss on a type that does not, output-compared vs `go run`; the pre-fix converter panics at the assertion.)

**A named interface's DECLARATION site also records its same-package structural implementers.** The assertion-site recorder above fires only where a package itself performs the assertion, so it generates an adapter only when the ASSERTING package can NAME the concrete dynamic type. When it cannot, the code compiles yet the assertion misses at run time: `math/bits` asserts `err.(runtime.Error)` on `runtime`'s `overflowError`/`divideError` panic values, whose dynamic type is the unexported `errorString`. `runtime` never itself casts `errorString` to `runtime.Error` — its plain `error` casts recorded only `errorString → error`, never `errorString → runtime.Error`, which `errorString` satisfies STRUCTURALLY through its value-receiver `RuntimeError()` method — and `math/bits` cannot name the unexported `errorString`, so no site ever recorded the pair and `err.(runtime.Error)` returned `ok = false`, NRE-ing on `e.Error()`. `visitInterfaceType` now, at each non-lifted non-constraint named-interface DECLARATION, records a `GoImplement` for every SAME-PACKAGE concrete type whose method set structurally satisfies the interface (`recordLocalConcreteImplementers`), sharing the value-then-pointer `recordIfImplements` probe with the assertion-site recorder. The producer runs in the concrete's HOME assembly — exactly where the go2cs-gen adapter must be generated — so the fix needs zero generator/`golib` change; `errorString` records against `ΔError` in `runtime`'s own `package_info.cs` and `math/bits` resolves through it. The scan is deliberately EAGER: same-package, non-interface, non-generic concrete types, structural (no cast required), with NO gate on the assertion actually occurring and NO gate on the concrete being exported (`errorString` is unexported yet escapes via the exported panic value). It COMPLEMENTS the assertion-site recorder (commit fcfe4a948) — which still covers cross-package concrete dynamic types the declaration-site scan cannot see (an external `p_test` assertion, or an imported dynamic type).

Measurement (an isolated A/B full-stdlib reconvert) put the blast radius at just **102 net-new `GoImplement` records, 7.1 % over a 1442-record base** — no VOLUME gate was needed. Most of the gross additions are a CLEANUP, not new surface: a concrete→interface pair a downstream consumer used to record redundantly at its own cast site (`go/parser` held 49 `*ast.Ident`/… → `ast.Expr`/`Stmt`/`Decl` records) now lives once in the producing package (`go/ast`, +96), and consumers drop the duplicate through the existing `importedValueImplements` dedup — the record migrates to the assembly where the adapter belongs.

One narrow SHAPE gate WAS forced, by the build rather than by volume: `recordIfImplements` skips a pair whenever the go2cs-gen adapter could not FORWARD one of the interface's methods (`adapterCannotForward`). The generator names a forwarding target in exactly one step — `this.M()` for a method whose receiver is the type itself, or `this.Field.M()` for one reachable through a SINGLE embedded field (its receiver is that field's own type). Two shapes fall outside that and compile to an adapter referencing a non-existent member:
- a method PROMOTED FROM AN EMBEDDED INTERFACE field (go/types gives it an interface receiver) — **CS1929**, `context.afterFuncCtx`, whose `Deadline` promotes from `cancelCtx`'s embedded `Context` interface while `Done`/`Err`/`Value` are concrete `cancelCtx` methods;
- a method reached through TWO OR MORE embedded structs (its receiver is neither the type nor any direct-field type; the generator hops to the first field only, one level too shallow) — **CS1503**, a `rig{Device{Sensor}}` whose `Label` lives on the twice-embedded `Sensor` (guarded directly by the `CrossPkgUser` behavioral test).

The gate lives in the shared helper so both recorders honour it, and it only ever SHRINKS the recorded set — it drops zero pre-existing records (the compiling corpus is green without any of these shapes), so the corpus stays a strict subset of what already compiled: no new CS1929 or CS1503. This unblocked `math/bits`' `TestDiv*Panic{Overflow,Zero}` assertions (5 of 6 validate end-to-end through the reconstructed adapter; the 6th, `TestDiv32PanicZero`, is gated by an unrelated `golib` gap — a hardware `DivideByZeroException` is not re-presented as a Go `runtime.Error`, and `Div32` alone relies on the implicit division panic rather than an explicit `panic(divideError)`). (Guarded by the `OptionalInterfaceStructuralAssertion` behavioral test — a `widget` that structurally implements a narrower `Tagger` interface, never cast to it, held as `any` through a `[]any` and asserted to `Tagger`; the `any`-typed operand makes the assertion-site recorder record nothing, so the declaration-site producer is the SOLE source of the `widget → Tagger` adapter, output-compared vs `go run`.)

**A second gate is a NAME collision, resolved at write time.** The `adapterCannotForward` shape gate was measured against a corpus build that reported only the long-standing `reflect/value.cs` `.Clone` blocker — but MSBuild SKIPS a failed project's dependents, and `flag` and `compress/zlib` both reach `reflect` through `fmt`, so neither was ever built. With `.Clone` fixed, `compress/zlib` surfaced **CS0102 + CS0111 ×5**: go2cs-gen composes an adapter class name from the LAST DOT SEGMENT of each side (the same naming `adapterTypeRef`/`valueAdapterTypeRef` emit at cast sites), so `zlib`'s OWN `Resetter` and the `compress/flate` `Resetter` that its `*reader` also implements both compose `readerжResetter` in `zlib_package` — two `.g.cs` files declaring one class. The FORM is part of the name (the `ж` pointer prefix vs the `ᴠ` value infix), so only same-form pairs collide.

The loser must be chosen by ORIGIN, not by record order: `writePackageInfoFile` now tracks, per recorded pair, whether EVERY producer was the declaration-site structural recorder (`structuralOnlyImplementations`; a DEMANDED record from any emitted cast/assertion site wins permanently), and its collision prune drops a structural-only pair whose adapter name a demanded pair already owns. That is the safe direction: no emitted C# names a structural-only pair's adapter — it exists solely so a run-time assertion can resolve — whereas dropping a DEMANDED pair strands a real cast site on a class the generator never emits (CS0246). Two colliding structural-only pairs are broken by keeping the lexicographically first, so the outcome is deterministic regardless of map iteration order. A pair the ALIAS dedup will skip (the qualified duplicate of a record already carried under a package type alias) is excluded from ownership — `CrossPkgUser`'s `type Tagged = CrossPkgLib.Labeled` records `badge` under both names, and the qualified one would otherwise evict a local `Labeled` pair while itself emitting `badgeᴠTagged`. Corpus-wide the prune removes exactly one record. (Guarded by `CrossPkgUser`: `*dial` satisfies both the local `Labeled` and the foreign `CrossPkgLib.Labeled` and is cast to the foreign one, so the two pairs compose one `dialжLabeled`; without the prune the build fails `CS0102: 'main_package' already contains a definition for 'dialжLabeled'`.)

**RETIRED (2026-07-25) — the two recorders above, plus the test-package scan, are GONE; a named-interface assert now resolves at RUN TIME.** Everything from "An assertion to a NAMED interface must record…" through the ORIGIN paragraph is HISTORY, kept because it explains records that still exist. Its founding premise — that `TryTypeAssert` can only resolve a named target through a compile-time `GoImplement` adapter — stopped being true when the tiered interface shells landed (see *Every eligible interface carries runtime duck-typing shells*): `golib`'s `AdapterBinder` now constructs a wrapper for a structurally-matching dynamic value from the interface's OWN assembly, so the three STRUCTURAL producers — `recordAssertConcreteImplementers` (assertion site), `recordLocalConcreteImplementers` (declaration site) and `recordTestPackageImplementers` (`-tests` type-side scan) — were deleted along with `recordIfImplements`, `adapterCannotForward` and the `structuralOnlyImplementations` collision prune they existed to arbitrate (~495 lines). They were never sound, only useful: each GUESSED an assertion's dynamic type by enumerating types it could name, and was blind by construction to a dynamic type in a later-converted assembly (`io/fs` recorded `subFS`, never `os.dirFS`). What REMAINS is every DEMANDED record — an explicit conversion, a `var _ I = T{}` witness, a resolved-concrete empty-source assert — plus the `Promoted` and `ConstraintProxy` records; `ImplementGenerator` is untouched, so a declared conversion still gets its nominal adapter, still the ~1.1 ns fast path. The corpus effect, measured on a seeded 305-package reconvert: **1535 → 1324 records, −335 / +124 across 53 packages**. Of the 335 dropped, 73 RELOCATE — the pair moves from the provider package to each consumer that demands it, so the emitted reference changes shape (`io.SectionReaderжReader` → `io_SectionReaderжReader`, and `(Scored)(Verdict)4` → `new CrossPkgLib_VerdictᴠScored(…)`) — and 262 are simply gone; a scan of all 1919 corpus `.cs` finds **261 of those 262 named by no emitted C# whatsoever**, which is the direct measurement of how speculative they were. Of the 124 added, 117 are the consumers' half of those relocations and 7 are base-interface records the interface-inheritance prune had been suppressing under a now-deleted derived-interface record (`flag.textValue → Value`, `net.UnknownNetworkError → error`, `runtime.errorString → error`, …). (Guarded by five EXISTING behavioral tests that now carry ZERO nominal records for the pairs they assert — `DerivedInterfaceStructuralProbe`, `OptionalInterfaceStructuralAssertion`, `InterfaceToInterfaceAssertion`, `AnonIfaceThroughPointerAdapter`, `IfaceToIfaceNarrow` — so their output comparison IS the shell-resolution proof; and operationally by the banked suites, `testing/quick` 8/8 being the direct one, since its `myStruct → Generator` record was the test-package scan's sole consumer.)

**A local named-FUNC value record is exempt from the interface-inheritance prune.** Independently, `flag` failed **CS0246** on `boolFuncValueᴠValue` — a PRE-EXISTING defect the same masking hid, reproducible with the structural recorder reverted. A C# delegate cannot be a partial struct, so a `GoImplement` pair whose concrete is a named func type generates a per-interface `ᴠ` ADAPTER CLASS rather than an entry folded into the type's own base list. `flag`'s `boolFuncValue` is recorded against both `boolFlag` and `Value`, and `boolFlag` EMBEDS `Value`, so the subsumption prune dropped the `Value` pair as "covered by inheritance" — true for a partial struct, false for an adapter class, which is per-exact-interface. The ж-pointer form and the foreign-value form were already exempt (`adapterClassImplementations`); the local named-func value form now registers there too, so `flag.cs`'s `new boolFuncValueᴠValue(…)` keeps its record. This is the same reasoning that exempts `new net_ConnᴠWriter(…)`, applied to the one adapter-class shape the list had missed.

**The run-time structural match for an ANONYMOUS interface is SIGNATURE-aware, not name-only.** *(Ladder detail SUPERSEDED — `ᴛAs` was retired 2026-07-25 in favour of the duck-typing shells, and the two memoized tiers were folded into one per-interface itab cache on 2026-07-26; both are described under* Every eligible interface carries runtime duck-typing shells*. The signature-aware matching rule below is unchanged and current — only the tier it gates has moved.)* An assertion to an anonymous (dynamically declared) interface — `x.(interface{ Unwrap() []error })` — does not resolve through a compile-time `GoImplement` adapter; it resolves at run time in `golib`, where `builtin.TryTypeAssert` gates on `Cache<TInterface>.Implements` before invoking the generated `ᴛAs` conversion. That gate used to compare only method **names**, so two anonymous interfaces that share a name but differ in signature — `errors.Is`'s emitted `is_typeᴛ1 { error Unwrap(); }` and `is_typeᴛ2 { slice<error> Unwrap(); }` — were CONFLATED: a value whose `Unwrap` returns `[]error` matched *both*, and constructing the wrong adapter (`error Unwrap()` bound to a `[]error`-returning method) threw `NotImplementedException` from the adapter's static constructor. The check now matches each interface method by NAME **and SIGNATURE** (parameter and return types), scoped to the value's actual Go method set: `TypeExtensions.StructurallyImplements` resolves the value's receiver element (a pointer box `ж<X>` exposes X's value- and pointer-receiver methods; a plain value `X` exposes only its value-receiver methods) and requires, for every interface method, an extension method with the same name whose signature matches (the candidate's first parameter is the receiver, which the interface method lacks). This also fixes a second defect on the SAME path: `GetExtensionMethodNames` collapses a closed `ж<X>` to the open `ж<>` generic definition (correct for MinBy-precedence single dispatch, wrong for a name-set membership test), so the old check admitted the pointer-receiver methods of *every* type — e.g. `ж<errorString>`, whose `errorString` has no `Unwrap`, matched `is_typeᴛ1` because `fmt`'s `*wrapError.Unwrap` was in the collapsed set. Open-generic receiver methods (methods on a Go generic type, whose signature carries type parameters that cannot be compared against a concrete interface signature) keep the prior name-only match. On the same fix, `error._<T>()` (the single-value assertion `err.(T)` on an `error`) now routes an INTERFACE target `T` — including a dyn anonymous interface — through this general machinery instead of casting the carrier to `error<T>` (which threw `InvalidCastException` when the dynamic value was a generated pointer/interface adapter rather than an `error<T>`), first unwrapping a pointer-sourced `IжAdapter` to its receiver box so the structural probe sees the dynamic `*T`. This unblocked `errors.Join`/`TestJoin` end-to-end and made `errors.Is` route wrapped/multi errors to the correct `Unwrap` arm. (Guarded by the `AnonInterfaceSignatureAssert` behavioral test — a value whose `Unwrap` returns `[]error` and one whose `Unwrap` returns `error`, each asserted against BOTH `interface{ Unwrap() error }` and `interface{ Unwrap() []error }`; the correct shape matches and dispatches, the wrong one misses. Output-compared vs `go run`; the pre-fix `golib` crashes the C# process at the conflated assertion.)

**An interface that EMBEDS another interface must collect only the base's INSTANCE, ORDINARY members — reflection's default `BindingFlags` leak the base's STATICS.** Go's `interface{ error; Temporary() bool }` (`net.Error`'s shape) converts to a `[GoType("dyn")] partial interface classify_type : error`, so both halves of the duck-typing machinery have to walk the C# base interface to see `Error()`. Both walks collected too much. golib's `TypeExtensions.GetInterfaceMethods` called `baseInterface.GetMethods()` with **default** flags (`Public | Instance | Static`) where the direct-member call above it correctly passes `BindingFlags.Public | BindingFlags.Instance` — and golib's hand-written core interfaces expose static duck-typing conversion helpers (`error.As<T>`, `fmt.Stringer.As<T>`), while `TypeGenerator` stamps `ᴛAs` onto every dyn interface. So `StructurallyImplements` demanded a static `As` from the dynamic value's Go **method set**, which no Go type can ever satisfy: *every* embedding interface's structural probe answered FALSE, and an assert against a value that plainly has both methods returned a MISS. Symmetrically, go2cs-gen's `InterfaceDeclarationSyntaxExtensions.GetInterfaceMethods` walked `AllInterfaces` members with no `IsStatic` / `MethodKind` filter, so the Δ wrapper FORWARDED those statics — the two surviving `As` overloads emitted duplicate `AsByPtr`/`AsByVal` delegate types and `s_AsByPtr`/`s_AsByVal` fields (**CS0102** ×6, so the project did not even compile). Both walks now filter to instance, ordinary members; property/event accessors and constructors are likewise never Go interface methods. Note the base walk must stay TRANSITIVE — .NET's `Type.GetInterfaces()` already flattens an inheritance chain, so a 3-deep `interface{ named; Depth() int }` → `named : stringish` collects all three levels. (Guarded by the `DerivedInterfaceStructuralProbe` behavioral test: an anonymous interface embedding `error` asserted against a `*tempErr` that has `Error`+`Temporary` and a `*plainErr` that has only `Error`, plus a 3-deep embedding chain with a deepest-level negative control, output-compared vs `go run`. Pre-fix the project fails to compile; with only the gen half fixed it compiles and prints `not temporary` where Go prints `temporary=true msg=boom`.)

**A typed-error assert (`err.(*T)` / `err.(T)`) resolves through the SAME machinery — the `error<T>` carrier is only a special case.** `errorExtensions._<T>(this error)` is the overload C# picks whenever the assert operand is statically an `error` (`builtin._<T>(this object)` is less specific), and its body used to be a direct cast, `((error<T>)target).Target`. That only ever matched `golib`'s own reflective carrier — the object `error.As` builds — which **converted code never constructs**: an `error` produced from a Go pointer is a generated `IжAdapter` (`new fs.PathErrorжerror(Ꮡ(new PathError{…}))`), and one produced from a value is the implementing struct itself. So every single-value typed-error assert against real converted code threw `InvalidCastException` ("`PathErrorжerror` to `error<ж<fs.PathError>>`") — and because that is a raw CLR fault rather than a `PanicException`, Go's `recover` could not even see it. `os`'s `dirFS.Open` path-fixup (`err.(*PathError).Path = name`) died on it, taking `io/fs`'s `TestGlob`, `TestReadDirPath` and `TestReadFilePath` down as infrastructure errors. The comma-ok form was never affected: `err._<T>(ᐧ)` has no `errorExtensions` overload and already bound to `builtin`'s adapter-aware `TryTypeAssert`. `_<T>` now unwraps the `error<T>` carrier when the dynamic value actually IS one, and otherwise defers to `((object)target)._<T>()` — the one type-assertion machinery — so a statically-`error` operand and an `any`-typed one can never disagree about the same assert. Commit `cb0f58078` (above) closed the INTERFACE half of this defect; this closes the CONCRETE-type half, and the two runtime-`Type` overloads (`err._(type)`, `err._(type, out result)`) were routed through `builtin.TryTypeAssert(object, Type, out object)` for the same reason, replacing a reflection lookup of `error<>`'s explicit conversion operator that had the identical carrier-only blind spot. (Guarded by the `TypedErrorAssertThroughAdapter` behavioral test — pointer- and value-sourced errors asserted back to their concrete types through an `error` boundary, a write through the asserted `*T` observed on the interface value, comma-ok misses in both directions, and a failed single-value assert proven to be a *recoverable* panic; output-compared vs `go run`, and the pre-fix `golib` crashes the C# process with exit code 2.)

**Constructing the run-time duck-typing wrapper is FAIL-SOFT — a structural false positive must MISS, not crash.** golib's structural probe is deliberately imprecise in one place: for an **open-generic receiver method** it matches by NAME ONLY, because the candidate's signature carries the receiver's type parameters and cannot be compared against the interface's concrete signature (see `StructurallyImplements`' remarks). So a Go generic's `Get() T` matches `interface{ Get() string }` for *every* instantiation — `box[int]` included, where Go plainly says no. The probe therefore said yes, `builtin.TryTypeAssert` closed the generated `ᴛAs` over `box<int>` and invoked it, the wrapper's static initializer found no bindable extension overload and threw `NotImplementedException`, and reflection re-wrapped that as `TargetInvocationException` — which escaped the assertion as a process crash (`Exception has been thrown by the target of an invocation.`, exit 2) where `go run` prints a clean comma-ok miss. A MISS is normal control flow at every emitted assertion and type-switch site (the method's own remarks say so), so wrapper construction now runs through one fail-soft helper (`TryConstructInterfaceWrapper`) covering both close-over paths — by-value and by-pointer over a receiver box — and any construction failure answers `ok=false`: a `MakeGenericMethod` constraint violation, an already-faulted type initializer, a conversion whose result is not the asserted interface (the hard `(T)` cast became an `is T` pattern), or missing dynamic-code support. This is the same discipline golib's `CreateInterfaceHandler` already applies. It narrows nothing: a genuine duck-typed assertion still resolves and dispatches. (Guarded by the `StructuralAssertFailSoftMiss` behavioral test — a real match then a false-positive probe, on each close-over path; pre-fix the C# process dies on the second line.) **Superseded 2026-07-25** — the `ᴛAs` route this helper closed, `TryConstructInterfaceWrapper` itself, and `CreateInterfaceHandler` were all retired when anonymous interfaces moved onto the runtime duck-typing shells (see *Every eligible interface carries runtime duck-typing shells…*). The fail-soft rule is unchanged and now lives in one place, `AdapterBinder.TryCreate`; the guard still holds.

**CLOSED 2026-07-25 (kept as the measured statement of the problem) — a NAMED interface had no run-time wrapper, so a cross-package structural satisfaction missed.** Every named interface now carries runtime duck-typing shells (see *Every eligible interface carries runtime duck-typing shells…* below), `io/fs` validates 18/18, and the `ᴛAs` machinery described here no longer exists — anonymous interfaces use the same shells. The two recorders above (assertion-site and declaration-site) can only see the *converting* package's scope, so an assert whose dynamic type comes from a package converted **later** — or from one the asserting package does not import at all — records nothing, and the run-time fallback cannot cover for it: `builtin.TryTypeAssert`'s structural path needs the generated `ᴛAs`/`Δ<Iface><T>` duck-typing wrapper, which `TypeGenerator` emits **only** for `[GoType("dyn")]` (anonymous) interfaces — 33 of them corpus-wide, against 267 named ones. Measured ground truth (`io/fs`, 2026-07-24): `os.dirFS` structurally satisfies `fs.ReadDirFS`/`fs.StatFS` and `ж<os.File>` satisfies `fs.ReadDirFile`, and golib's structural PROBE agrees — `builtin.Implements<fs.ReadDirFS>(dirFS)` returns `true` for all three — yet each assert returns a MISS, because `GetInterfaceConversionMethod` finds no `ᴛAs` on the named target and bails. `io/fs`'s `ReadDir` therefore falls back to `fsys.Open` + `file.(ReadDirFile)`, misses again, and returns `readdir .: not implemented` with zero entries; `fs.Glob` swallows that error (`TestGlob` sees an empty match set) and `fs.WalkDir` hands it to the callback (`TestIssue51617` sees only `.`). Nominal recording can never be complete here — an interface's dynamic type may live in any assembly, including one built after the asserting package — so the durable fix is to give NAMED interfaces the same run-time duck-typing wrapper `dyn` ones already get, with method collection made TRANSITIVE (a named interface inherits members through C# interface inheritance: `ReadDirFS : FS` needs `Open` as well as `ReadDir`). That is a corpus-wide `go2cs-gen` change and is **not yet done**; `io/fs` validates 16/18 with these two tests open.

The types that support these tuple-returns are defined in the [`golib`](https://github.com/ritchiecarroll/go2cs/tree/master/src/core/golib) library; ordinary user-code tuple returns convert as normal C# tuples without special handling.

**A package-level `var a, b = f()` reads ValueTuple components.** C# static field initializers cannot deconstruct a tuple, so the per-name field emission assigned the WHOLE result tuple to the first field (CS0029 — edwards25519's `var identity, _ = new(Point).SetBytes(…)`). With exactly one non-blank name the component read is appended to the inline call (`internal static ж<Point> identity = …SetBytes(…).Item1;` — blank names keep their uninitialized `_ᴛNʗ` fields, and the call still runs once). With two or more non-blank names the call is evaluated ONCE into a hidden tuple field and each name reads its component (`internal static (nint, @string) tupleᴛ1ʗ = pair(); internal static nint n = tupleᴛ1ʗ.Item1;` — C# static initializers run in textual order, so the reads follow the temp). Gated to package scope, no explicit type, one call initializer typed as a tuple; in-function `var x, y = f()` keeps the existing path. (Guarded by the `GlobalTupleVarDecl` behavioral test — both shapes plus a call-count probe proving single evaluation, output-compared vs Go.)

**Every trailing argument of a variadic pointer parameter gets the box treatment.** The per-parameter argument loop visits declared parameters only, so `checkInitialized(p, q)` binding two deref-aliased pointer parameters to `...*Point` boxed only the first (`checkInitialized(Ꮡp, q)` — CS1503). The pointer-argument box treatment now fans out from the variadic parameter's index to every trailing argument, mirroring the type-parameter `@string` fan-out; the spread form (`f(s…)`) is excluded as before, and non-variadic calls are byte-identical. (Guarded by the `VariadicPointerParam` extension `pairTotal` — three deref-aliased pointer params forwarded to the variadic, value vs Go.)

**A call-result delegate of a NAMED func type must resolve its signature through `Underlying()`.** All the per-argument treatments above (pointer boxing, interface conversion, `u8` suppression) are driven by `getFunctionSignature`, which for a callee that is itself a call — `valueEncoder(v)(e, v, opts)`, encoding/json — read `info.TypeOf(fun).(*types.Signature)`. When the inner function returns a **named** methodless func type (`valueEncoder` returns `encoderFunc`), `info.TypeOf` is a `*types.Named`, so that assertion failed and the signature came back nil — the per-argument loop never ran, and the pointer receiver `e` (a deref'd `ref var e = ref Ꮡe.Value`) passed its value alias where the `ж<encodeState>` slot wanted the box `Ꮡe` (CS1503). The `*ast.CallExpr` arm now asserts on `Underlying()`, looking through the named func type to its signature (a no-op when the result is already an unnamed signature). Byte-identical across the behavioral corpus and across an A/B of encoding/json+gob+text/template+net/http+reflect — a single line moves (json's `valueEncoder(v)(e,…)` → `(Ꮡe,…)`). (Guarded by the `NamedFuncResultPointerArg` behavioral test — `adder()` returning a named `addFunc` called immediately with a `*State` receiver that must box, mutation through the box observed vs Go.)

**A variadic *closure* rebinds its `params` array to a slice at the top of its body.** A variadic parameter `a ...T` arrives in C# as a `params ꓸꓸꓸT` array named `<name>ʗp` (a distinct name, so it doesn't collide with the slice the body expects); the body then references the bare `<name>` as a `slice<T>`. A top-level function emits a `var <name> = <name>ʗp.slice();` prologue as its first block statement, but a **function literal** emitted no such prologue, so any closure that referenced its variadic parameter used an undefined bare name (CS0103 — internal/dag's `errorf := func(format string, a ...any) { … fmt.Sprintf(format, a...) }` spread `a…` against a name that was never declared). A function literal now emits the same rebinding prologue. Because the prologue is prepended *before* the single-return→expression-body collapse, a variadic closure whose body is a lone `return f(a...)` keeps its block form (the rebound name is a statement-scoped local) rather than collapsing to an undefined expression. An **IIFE** literal is excluded — it emits parameter *names* only (the raw `a`, with the delegate cast supplying the `params` type), so there is no `<name>ʗp` array to `.slice()`. (Guarded by the `VariadicClosureSpread` behavioral test — a single-return closure spreading `a...` into `fmt.Sprintf`, a closure ranging its variadic slice, and a single-return closure forwarding `a...` to another variadic; output-compared vs Go.)

**A non-escaping variadic parameter binds through the stack-only `sslice<T>` view.** The C# signature
already receives the arguments as `params Span<T>`, so a prologue whose uses are proven frame-local now
emits `var xs = xsʗp.sslice();` instead of `xsʗp.slice()`. The old `Span<T>.slice()` path calls
`ToArray()`; the stack view removes that allocation and, for a spread call `f(s...)`, keeps the callee
aliased to the caller's backing array as Go requires. The proof is intentionally narrow: direct
`len`/`cap`, element indexing, and range are eligible. Passing or spreading the slice, assigning or
returning it, `append`, address-taking, slicing, interface boxing, channel/go use, or a nested function
literal falls back to the heap `slice<T>`. When defer/recover requires an execution wrapper, the converter
passes the incoming params `Span<T>` by ref through the one-reference `func` overload (whose generic
reference slot uses C# 13's `allows ref struct` anti-constraint) and performs the slice rebinding
inside that wrapper. The wrapper therefore does not capture the outer ref-like parameter, and an
otherwise-eligible body keeps `.sslice()` plus Go spread aliasing. Uses that genuinely require a heap
slice still fall back to `.slice()`. Function literals use the same eligibility map and ref-wrapper path.
Golib's `cap<T>(in sslice<T>)` complements the existing `len` overload; `append` deliberately has no sslice
grow path, so any append remains a heap case. (Guarded by `VariadicPointerParam`: safe range/len/cap/index
sites emit `.sslice()`, a spread element replacement is observed through the caller's slice against
`go run`, defer-wrapper declarations and closures pass the params Span by ref, preserve spread element
writes, and nested captures still pin the `.slice()` fallback.)

**The `ꓸꓸꓸT` alias identifier mirrors the GO name; its referent must be using-independent.** The readable `params ꓸꓸꓸT` form is a namespace-scope C# using alias (`using ꓸꓸꓸstring = Span<@string>;`), so it applies only where a legal alias identifier exists — everything else falls back to the inline `params Span<T>`. Two separate constraints decide that, and only the first is about the *name*. **(1) The identifier** cannot contain `<`, `>` or `.`, so it transliterates the element's **Go** name, joining any package qualifier with `TypeAliasDot` — Go's `ps ...unsafe.Pointer` becomes `params ꓸꓸꓸunsafeꓸPointer psʗp`, the same `pkgꓸType` convention `package_info.cs` already uses for its global usings. Go names are preferred over the emitted C# ones: they need no `@` keyword-escape stripping (`@` is legal only at identifier start — `ꓸꓸꓸ@string` is a lex error, CS1002/CS0116 — while the `Span<>` referent keeps the escape), carry no `_package` class suffix, and undo a Δ collision-rename, so go/types' `...Type` reads `ꓸꓸꓸType` rather than `ꓸꓸꓸΔType`. Any Go identifier is a legal C# one and the ellipsis prefix defuses keywords (`...event` → `ꓸꓸꓸevent`); types with no Go name — a basic type, a universe type (`error`), a lifted anonymous struct (internal/fuzz's `CorpusEntry`) — transliterate the emitted C# name instead. Because only the *name* is constrained, a qualified referent still reads like Go: a **same-package** element is qualified with the package class (a bare nested name like `statDep` does not resolve at namespace scope, CS0246) yet still emits `params ꓸꓸꓸShape shapesʗp` over `using ꓸꓸꓸShape = Span<main_package.Shape>;`. **(2) The referent** is resolved by C# with the compilation unit's own using directives *not* in effect, so it may not name another file-local alias. A cross-package element renders in the short alias form (`@unsafe.Pointer`, `ast.Expr`), which is exactly that — left as-is it fails CS0246, and `go2cs-gen` (which copies these usings into its generated files) cannot resolve the symbol either and falls back to unescaped text, `Span<unsafe.Pointer>`, whose bare keyword cascades to CS8956. Such a referent is therefore rewritten to the alias's own target — `Span<unsafe_package.Pointer>`, `Span<global::go.go.ast_package.Expr>` — which is using-independent by construction, being what the `using <alias> = <target>;` line itself resolves. The mapping is recorded where the import using is *emitted* rather than re-derived, so the `-tests` package-under-test rebinding is honored automatically; an alias not yet bound when the declaration is visited (visitFile synthesizes canonical aliases for inference-only foreign references *after* the walk) degrades to the inline form. A **pointer** element is the one constructed form that does transliterate: go2cs already writes `*T` as `ж<T>`, so `bs ...*box` reads `params ꓸꓸꓸжbox bsʗp`. Its pointee resolves through the same routine and so takes the same namespace-scope qualification — the alias referent says `Span<ж<main_package.box>>` where the inline form could say bare `ж<box>`, since only the inline form sits inside the package class — and the two rules compose, giving go/doc's `...*ast.File` a `ꓸꓸꓸжastꓸFile = Span<ж<global::go.go.ast_package.File>>`. A pointee with no alias form of its own (a type parameter, a constructed pointee such as `*[]byte`) takes the whole element inline with it. Genuinely inline-only, then: a **type parameter** (never in scope inside a referent — `First<T>(params Span<T> valsʗp)`) and every **other constructed** element, for which no established transliteration exists (`map<@string, any>`, `slice<byte>`, `Action<ж<options>>`). Two element types transliterating to one identifier in a single file would bind it twice (CS1537), so the first claim wins and the loser stays inline. Deriving from Go names is also what keeps os/signal's long-standing `using ꓸꓸꓸosꓸSignal = Span<osꓸSignal>;` and text/template's `ꓸꓸꓸreflectꓸValue` **byte-identical** through this path — they already followed the convention, because their bare C# names come from **global** usings in `package_info.cs`, which *do* carry over into a using-alias referent. (Guarded by the `VariadicSlotInterfaces` behavioral test — a same-package interface element — and by `VariadicPointerParam`, which pins all three outcomes in one file: an aliased pointer (`ꓸꓸꓸжbox`), an aliased cross-package element (`ꓸꓸꓸunsafeꓸPointer`), and an inline constructed one (`Span<slice<byte>>`); the type-parameter arm of that fallback is held by `GenericVariadicFunc`. All output-compared vs Go. Stdlib footprint: every previously-inline site becomes an alias except the type-parameter and non-pointer-constructed ones — same-package elements read bare (`ꓸꓸꓸAttr`, `ꓸꓸꓸtraceArg`, `ꓸꓸꓸWriter`, `ꓸꓸꓸType`), cross-package ones carry their qualifier (`ꓸꓸꓸunsafeꓸPointer` in runtime, `ꓸꓸꓸastꓸExpr` in go/types), and pointers take the `ж` form — `unicode.In(r, ranges ...*RangeTable)` becomes `params ꓸꓸꓸжRangeTable rangesʗp`.)

**A named result is DECLARED only when something reads it (2026-08-08).** Go's named results are ordinary addressable locals, so the converter declared every one of them at function entry. But Go names results for *documentation* far more often than it uses them — `func EncodeRune(r rune) (r1, r2 rune)` never mentions `r1`/`r2` again — and the emitted `rune r1 = default!;` is then dead on arrival. That one shape was **1,218 of the corpus's 1,219 `CS0219`** ("assigned but its value is never used") warnings. Nothing is lost by omitting it, because the names survive exactly where a reader reads them — on the C# tuple return type — so the emission moves closer to the Go source, not further from it:

```csharp
// before
public static (rune r1, rune r2) EncodeRune(rune r) {
    rune r1 = default!;
    rune r2 = default!;

    if (r < surrSelf || r > maxRune) {

// after — the names still read off the signature
public static (rune r1, rune r2) EncodeRune(rune r) {
    if (r < surrSelf || r > maxRune) {
```

The declaration is kept whenever anything can read it, and the check is deliberately conservative in every unclear case — a retained dead declaration costs one line, a dropped live one is `CS0103`. It stays when the body **references** the result (read, assigned, address-taken, or captured by a closure — a capture is a use, so that walk descends into function literals); when the body has a **naked `return`**, which reads every named result by definition (that walk stops at a nested `*ast.FuncLit`, whose bare returns belong to the literal — the `iter.Pull` shape above depends on this); when the result is **heap-box backed**, whose box is the storage the render sites reference; and when the function is lowered through either defer form — `namedReturnDeferMode`, where the declarations sit outside the `func()` wrapper precisely so deferred closures can mutate them, or a **GoFrame**, whose named exit emits a trailing `return <names>;` after the `try`. In those last two the *generated* code reads the locals and the Go body need never mention them, so liveness is switched off wholesale rather than inferred. The same rule and the same opt-out apply to function **literals** (`namedReturnDeclLines`). Keeping the check rather than suppressing the code corpus-wide also preserves `CS0219` as a live signal: a genuinely dropped assignment to a named result still surfaces. (Guarded by `namedResultLiveness_test.go`, which pins one function per liveness reason plus the dead shape and the outer-dead/inner-naked case, and is proved in BOTH directions by negative control. Corpus effect: `CS0219` 1,219 → **52**, none of them a named-return prologue — 33 are in hand-owned files the converter never re-emits, 18 are Go's own `var` witnesses for a constant-folded `unsafe.Sizeof`/`Offsetof`, and 1 is a folded local const.)

## Slices and Arrays
Go slices and arrays are converted to the golib [`slice<T>`](https://github.com/ritchiecarroll/go2cs/blob/master/src/core/golib/slice.cs) and [`array<T>`](https://github.com/ritchiecarroll/go2cs/blob/master/src/core/golib/array.cs) structures. A `make`-style allocation uses a constructor; a composite literal builds a C# array and projects it with the `.slice()` / `.array()` extension:

```go
package main

import "fmt"

func main() {
    primes := [6]int{2, 3, 5, 7, 11, 13}   // array literal
    nums := []int{10, 20, 30}              // slice literal
    buf := make([]byte, 4)                 // make
    fmt.Println(primes[0], nums[2], len(buf))
}
```

converts to:

```csharp
internal static void Main() {
    var primes = new nint[]{2, 3, 5, 7, 11, 13}.array();
    var nums = new nint[]{10, 20, 30}.slice();
    var buf = new slice<byte>(4);
    fmt.Println(primes[0], nums[2], len(buf));
}
```

A **named** slice/array type (`type d [3]rune`, `type s []int`) lowers to a struct wrapping `array<T>`/`slice<T>`; its composite literal cannot use C# collection-initializer braces (the lowered struct has no `Add`), so it is constructed through the underlying-collection constructor: `d{0, 32, 0}` → `new d(new rune[]{0, 32, 0}.array())`.

The **empty** composite of such a type is its **zero value**, not a one-element literal: the generic named-composite `nil` filler (which gives a named *struct* composite its `new T(nil)` zero-value ctor argument) previously landed *inside* the element literal — `tmpBuf{}` (`type tmpBuf [32]byte`, runtime `string.go`'s `*buf = tmpBuf{}`) emitted `new tmpBuf(new byte[]{nil}.array())`, a `NilType` element in a `byte[]` (CS0029). An empty **array** composite now emits a zeroed *fixed-length* backing — `new tmpBuf(new byte[32].array())` — because Go's `[N]T{}` is a full-length zero array (an empty `{}` literal would produce a length-0 backing); an empty named-**slice** composite emits an empty non-nil backing — `pm{}` → `new pm(new uint32[]{}.slice())`. (Guarded by the `NamedArrayWrapper` extension — empty array composite read/written at full length, zeroing an existing wrapper through a pointer via `*buf = tb{}`, and an empty slice composite appended to; values vs Go. The nil-vs-empty *distinction* — `pm{} == nil` is `false` in Go — is a separate pre-existing golib model latent: the slice nil-compare conflates nil with empty-but-allocated.)

**Reslicing SHARES the backing array — golib `slice<T>` stores capacity.** A Go reslice is a view adjustment, never a copy: writes through `s[a:b]` are visible through `s` (and vice versa), `append` within capacity writes the shared backing in place, and only `append` beyond capacity reallocates and detaches. The emitted forms are the C# range indexer for 2-index expressions (`s[a:b]` → `s[a..b]`, `s[a:]` → `s[a..]`, `s[:b]` → `s[..b]`, `s[:]` → `s[..]`) and the golib `.slice(low, high, max)` extension for 3-index expressions (`s[a:b:c]` → `s.slice(a, b, c)`; a missing low is the `-1` sentinel). Both take bounds **relative to the view** (the source slice may itself sit at a non-zero offset in its backing array), default a missing high to `len(s)` — the range indexer resolves a from-end `Index` against the slice *length*, so `s[1..]` is Go's `s[1:]` even when `len < cap` — allow high up to `cap(s)`, and panic Go-style (`RuntimeErrorPanic.SliceBoundsOutOfRange`) when out of range. To represent a capacity-restricted 3-index view (`s[a:b:c]`, `c` below the backing array's end) without copying, `slice<T>` stores `m_capacity` as its own field rather than deriving it from the backing array's length; the `slice<T>(T[] array, nint low, nint high, nint max)` constructor builds such views. (Historically golib *copied* on any reslice that didn't span the whole backing array — a `base[2:5]` sub-slice was a detached array with lost aliasing — derived capacity from the backing end, and mis-measured `Available`/`Append` for non-zero-offset views. Guarded by the `SliceAliasing` behavioral test — `copy` into and element writes through a `low>0` reslice, reslice-of-reslice offset compounding, slice-of-array reslices, restricted-capacity 3-index writes, in-place vs reallocating `append`, all read back through the base and value-compared vs Go.)

**A `make` length/capacity/size-hint of a non-`int` integer type is cast to `nint`.** The golib allocating constructors all take `nint` — `slice<T>(nint length, nint capacity)`, `map<K,V>(nint capacity)`, `channel<T>(nint capacity)` — and C# does **not** implicitly convert a `uintptr`/`uint`/`uint32`/`uint64`/`int64` (C# `nuint`/`uint`/`ulong`/`long`) to `nint`. So `make([]byte, n/goarch.PtrSize)` with a `uintptr` length would leave `new slice<byte>(n / …)` with no applicable constructor, and overload resolution falls onto `slice<T>(T[])` — reported as `CS1503` ("cannot convert `nuint` to `byte[]`"); a map/chan with a `uintptr` hint is a direct `nuint`→`nint` CS1503. The converter casts each such length/capacity/hint argument to `nint`: `new slice<byte>((nint)(n / goarch.PtrSize))` (both args of `make([]byte, l, c)`), `new map<K,V>((nint)(hint))`, `new channel<T>((nint)(size))`. A plain `int` (`nint`) and an untyped-constant argument (`make([]byte, 4)` → a bare `4`) bind directly and are **left uncast** (no golden churn) — as are the widening `int8`/`int16`/`uint8`/`uint16` kinds. (Guarded by the `MakeSliceUintptrLen` behavioral test — `uintptr`/`uint`/`uint32`/`uint64` slice lengths, a `uintptr` len+cap, a `uintptr` map hint and chan size, and int/untyped controls, all `len`/`cap`/element values verified vs Go; runtime hits this in `mbitmap`'s `make([]byte, n/goarch.PtrSize)`.)

**Slicing a pointer-to-array.** Go lets a `*[N]T` be sliced directly — `p[lo:hi:max]`, `p[:]` — auto-dereferencing the array. The C# box `ж<array<T>>` has no slice/range members (its underlying `array<T>` does), so the converter dereferences first: `p[1:3:4]` → `(~p).slice(1, 3, 4)`, `p[:]` → `(~p)[..]`, `p[2:]` → `(~p)[2..]`. Without the deref the call binds to the box and fails (CS1929). The resulting slice shares the array's backing storage, matching Go. (The `(*[N]T)(ptr)[:n]` pointer-*cast* form is different — see *Pointer-cast slice* below.) A deref-aliased pointer **parameter** or **receiver** is the exception: it is emitted as the pointed-to *value*, not a box, so a `~` on it would deref a non-pointer (CS0023). When that value is a **named** array type — `b *pageBits` emitted `ref pageBits b`, where `pageBits` is `[N]uint64` — the wrapper has no slice/range members, so its underlying `array<T>` is reached via `.Value`: `b[:2]` → `b.Value[..2]`. When it is an **anonymous** array (`p *[N]T` → `ref array<T> p`) the value already *is* the `array<T>` and is sliced directly (`p[:]` → `p[..]`). Only a pointer-to-array **box** (a local, a field, a call result) gets the `~` deref. (Guarded by the `PointerArraySlice` behavioral test — local box, named-array receiver, and named-array parameter; runtime hits this in `select.go`'s `cas1[:ncases:ncases]` / `mprof.go`'s `stk[:n:n]` (locals) and `mpallocbits.go`'s `pageBits` receiver methods (`clear(b[:])`).)

**Named-slice pointer reinterpret (`(*[][]byte)(buf)` with `buf *Buffers`).** Go converts a pointer-to-named-slice to a pointer to its underlying slice type freely — net's `fd.pfd.Writev((*[][]byte)(buf))`, where poll's `Writev` *reslices the header through the pointer* (`consume` advances `*v`), and the caller must observe it. The C# boxes `ж<Buffers>` and `ж<slice<slice<byte>>>` are unrelated generic instantiations (CS0030), so the conversion emits a **field view over the wrapper's own backing slice**:

```csharp
consume(Ꮡbuf.of(Buffers.Ꮡm_value));
```

Two generator pieces make the view real aliasing: a named-slice wrapper's `m_value` is **mutable** (`ReadOnlyValue = false` — a readonly field would force a defensive copy and lose header writes), and `ISliceTypeTemplate` emits the field-ref accessor `internal static ref slice<T> Ꮡm_value(ref Buffers instance)` that `ж<T>.of()` projects through. Claimed narrowly: pointer→pointer, source pointee a NAMED type whose underlying is a slice identical to the (unnamed) target pointee. (Guarded by the `SortArrayType` extension `consumeOne` — a `(*[]Person)(&crew)` reinterpret whose reslice through the view shrinks the original `Roster`, runtime-verified against Go.)

The **reverse** direction — an underlying-slice pointer to a NAMED-slice pointer, `(*Buffer)(&b)` with `type Buffer []byte` (log/slog/internal/buffer's `sync.Pool.New`) — is **asymmetric**, because the projection above cannot run backwards: a named-wrapper box *contains* the underlying slice (project it out), but a bare-slice box does **not** contain a wrapper to project. It is emitted as golib's **storage reinterpret** instead — `Ꮡb.Reinterpret<slice<byte>, Buffer>()` — which re-views the *same slot* as the wrapper type rather than constructing anything: a generated named-slice wrapper is a single-field struct over the slice header, exactly the layout correspondence `ReinterpretAliasesStorage` recognizes, so the managed alias arm engages and the derived pointer ALIASES the addressed slice. A bare `(ж<Buffer>)(Ꮡ(b))` cast is CS0030 (unrelated instantiations). The source comes two ways and both render in BOX form (the `isPointer` ident context): an **address-of** arg (`&b`, `&h.field`) and an **existing pointer** arg (cryptobyte's `(*String)(out)` with `out *[]byte`). Claimed narrowly by the mirror gate: pointer→pointer, target pointee a NAMED type whose underlying is a slice identical to the (unnamed) source pointee.

It previously **constructed a wrapper box over a copy** — `Ꮡ(new Buffer(b))` — on the stated assumption that such a reinterpret is only ever used through the returned pointer. That assumption is false wherever the conversion exists precisely to write BACK, which is the shape's dominant corpus use, and the writes went nowhere:

| Site | What the copy cost |
|:--|:--|
| `log/slog` `commonHandler.withAttrs` — `(*buffer.Buffer)(&h2.preformattedAttrs)` | Every attribute `WithAttrs` pre-formatted was dropped, while the handler still advanced `groupPrefix`/`nOpenGroups` — so the JSON it emitted afterwards was unbalanced. Four `testing/slogtest` rows (`WithAttrs`, `multi-With`, `empty-group-record`, `resolve-WithAttrs`). |
| `crypto/tls` `readUint{8,16,24}LengthPrefixed` — `(*cryptobyte.String)(out)` with `out *[]byte` | An out-PARAMETER whose whole purpose is the write-back: the caller's slice never received the length-prefixed field. |
| `crypto/tls` `parseECHConfigList` — `(*cryptobyte.String)(&ec.PublicKey)` | A struct FIELD that stayed empty after a successful parse. |
| `vendor/…/cryptobyte` `ReadASN1Bytes` — `(*String)(out)` | Same out-parameter shape, same loss. |

Corpus A/B footprint of the correction: **5 files, 8 sites** (`log/slog/handler.cs`, `log/slog/internal/buffer/buffer.cs`, `crypto/tls/ech.cs` ×2, `crypto/tls/handshake_messages.cs` ×3, `vendor/golang.org/x/crypto/cryptobyte/asn1.cs`), plus the dead `ref var @out = ref Ꮡout.DerefOrNull()` locals the aliasing form no longer needs. (Guarded by the `NamedSlicePointerReinterpret` behavioral test — a direct `(*Buf)(&b)` **read back through `b`**, the closure-returned `func() *Buf { … return (*Buf)(&s) }` shape, a pointer-**parameter** `fillVia(out *[]byte)` read back through `out`, and a struct-**field** `(*Buf)(&h.preformatted)` appended across a reallocating growth and then truncated through a freshly derived pointer — output-compared vs Go.)

**Pointer-cast slice (`(*[N]T)(ptr)[:n]`).** A Go conversion that casts an `unsafe.Pointer` to a pointer-to-array and slices it produces a `[]T` over the pointed-to memory. It is emitted as the golib **`slice<T>`** — the C# representation of *every* `[]T` — built from a `ReadOnlySpan<T>` over the raw pointer: `new slice<T>(new ReadOnlySpan<T>((T*)ptr, (int)n))`. (Earlier it was a bare `Span<T>`, but a `Span<T>` does **not** range as `(index, element)` tuples — `for i := range s` → CS8130 — and has no `Ꮡ(s, i)` element-address — CS0411; `slice<T>` supports both, since it is `IArray<T>`.) The `ReadOnlySpan<T>` constructor takes a C# `int`, so a Go `int`/`uint` length (`nint`/`nuint`) is narrowed via `getRangeIndexer` (through the underlying for a named numeric); an int literal is left as-is. The slice **copies** the pointed-to memory (`ReadOnlySpan.ToArray()`), which is self-consistent for code that only uses the resulting slice (e.g. runtime's `printDebugLog` ranges `state` and writes `&state[i]`, never re-reading the raw buffer; `os_windows` ranges an unsafe `[]byte` read-only). Since this is always the `(*[N]T)(ptr)` unsafe-cast form, it is memory-layout-dependent code whose raw values flow through the `unsafe.Pointer`=`nuint` round-trip (a transient `fixed` address → not GC-stable), so the runtime values are not the contract — only compilable, rangeable, element-addressable C#. (Guarded by the `PointerCastSliceRange` behavioral Compile + target test — index range, value range, and `&s[i]` element-address over a pointer-cast slice; runtime greened `debuglog`'s `printDebugLog` and `os_windows`, ~25 errors via the cascade. The length narrowing is covered by `StdLibInternalAbi`.)

An **untyped (type-inferred) composite literal** — the inner `{…}` of a `[][]rank{ key: {…} }`, which has no explicit type node — is emitted as a target-typed `new(…)` when its inferred type is a struct (the struct constructor takes the field values). When the inferred type is a **slice or array**, that form is wrong (`slice<rank>`/`array<rank>` have no element-list constructor → CS1729); the converter emits the element-array projection instead — `{rA, rB}` (inferred `[]rank`) → `new rank[]{rA, rB}.slice()`, and an inferred `[2]int` → `new nint[]{…}.array()`. When the inferred type is a **pointer-to-struct** — the `[]*T{ {…} }` shorthand for `&T{…}` — it is emitted as the boxed struct constructor `Ꮡ(new T(field: val, …))` (a bare `new(…)` would target the box `ж<T>`, whose constructor lacks the struct's fields → CS1739). When such an untyped slice/array literal is **keyed** (`{joiningL: stateBefore, …}` — the inner `{…}` of x/net/idna's `joinStates = [][numJoinTypes]joinState{stateStart: {…}, …}`), the element-array projection above cannot take Go's `key: value` syntax — `new joinState[]{ joiningL: stateBefore }` is a C# array initializer, which has no keyed element form (CS1003 ×62). The keyed case is routed to a golib `golib.SparseArray<T>` collection initializer instead — `new golib.SparseArray<joinState>{ [joiningL] = stateBefore, … }.array()` (`.slice()` for a slice element) — the same form the *typed* keyed slice/array path emits (see below); the `.array()`/`.slice()` `IEnumerable<T>` extension materializes the dense backing, and a defined-integer key takes the `[(int)key]` cast exactly as in the typed path. (Guarded by `UntypedNestedSliceComposite`; runtime/lockrank.go's `lockPartialOrder` is a `[][]lockRank` and runtime1.go's `dbgvars` is a `[]*dbgVar` of the positional forms, and x/net/idna's `joinStates` is the keyed form.)

An **indexed (keyed) slice/array literal** — `[]string{lockRankSysmon: "sysmon", …}` — is emitted as a golib `golib.SparseArray<T>` collection initializer (`[index] = value`). Its indexer takes a Go `int`. When an index key's Go type is a **defined integer type** whose underlying type does not implicitly widen to C# `int` (i.e. `int`/`int64`/`uint`/`uint32`/`uint64`/`uintptr`, as opposed to `int8`/`uint8`/`int16`/`uint16`/`int32`), the key is cast to `int` so it satisfies the indexer (CS1503 otherwise): `[lockRankSysmon]` (a `type lockRank int`) → `[(int)lockRankSysmon]`. A key that already widens (e.g. a `uint8`-backed `Kind`) is left uncast.

A keyed slice/array literal whose element type is a **non-empty INTERFACE** routes its elements
through the interface-cast element loop (each value wraps via `convertToInterfaceType`) instead of
`convExprList` — and that loop rendered a `KeyValueExpr` as a FLAT `key, value` pair, feeding the
SparseArray collection initializer one item at a time (`Add(key)` then `Add(value)`:
CS1950 + CS1503 ×21 pairs on go/internal/gccgoimporter's `lookupBuiltinType`,
`[...]types.Type{gccgoBuiltinINT8: types.Typ[types.Int8], …}` — untyped named-const keys in a
function-body literal, immediately indexed). The loop now emits the same `[key] = wrappedValue`
indexer form the non-interface path produces, with the key routed through `sparseArrayKey` (so a
defined-integer-type key keeps its `[(int)key]` cast alongside the interface-wrapped value); a
keyed MAP literal with an interface element type reaching the same loop takes the identical
indexer form. (Guarded by the `SparseArrayIfaceElem` behavioral test — a function-body sparse
literal with untyped named-const keys immediately indexed, the package-level form, and a
named-`uint`-keyed form, elements read back and output-compared vs Go.)

A keyed (sparse, constant-index) literal of a **named array-wrapper** type — internal/trace/oldtrace's `timedEventArgs{1: uint64(ev.StkID)}` where `type timedEventArgs [4]uint64` — backs onto the golib `array<T>(length)` (which has an indexer setter), not a raw C# array. The wrapper's constructor takes an `array<T>` (the positional path already produces one via `.array()`), and the keyed elements render as the `[i] = v` indexed initializer — valid on `new array<uint64>(4){[1] = v}` but *not* on `new uint64[]{[1] = v}` (CS0131, an array-initializer takes no indexed members). A **positional** literal of the same wrapper keeps the `new uint64[]{…}.array()` form (unchanged — no churn). (Guarded by `NamedArrayKeyedLiteral` — a `type args [4]uint64` with multi-keyed, single-keyed, and positional literals, element reads output-compared vs Go.)

A **generic** named array type carries its type parameters (and their constraints) onto the forward declaration, and its element type is emitted fully qualified in the `[GoType]` attribute so the generated array-backed partial — which lives in a file without this file's package-relative `using` aliases — can resolve it:

```go
type table[T any] [3]atomic.Pointer[T]
```
```csharp
[GoType("[3]sync.atomic_package.Pointer<T>")] partial struct table<T>
    where T : new();
```

An **anonymous array/slice field whose element type lives in a multi-segment-path package** — `cpuLogWrite [2]atomic.Pointer[profBuf]`, `children [4]atomic.UnsafePointer` (atomic = `internal/runtime/atomic`) — keeps its `array<…>` wrapper. The field's type name is built structurally from the `[N]`/`[]` marker plus the recursively resolved element, *not* from the type's package-qualified string: that string (`[2]internal/runtime/atomic.Pointer[…]`) goes through a cross-package last-segment strip that would also remove the leading `[2]`, collapsing the field to the bare element type (`atomic.Pointer<…> = new(2)`) whose array `new(2)` initializer then mis-binds the element constructor (CS1503). With the structural rendering the field stays `array<atomic.Pointer<profBuf>> = new(2)`. An array of a current-package or basic-typed element was unaffected (its string has no foreign path to strip). (Guarded by the `ArrayOfCrossPackageType` behavioral test — `[3]atomic.Int32` / `[2]atomic.Uint64` fields; runtime's `trace`/`traceMap` structs hold these.)

#### A struct's array fields get their fixed length from a generated parameterless constructor

A Go `[N]T` array FIELD has a zero value of N zero elements — never nil. The converter emits the field
with a length initializer — `internal array<atomic.Int32> c = new(3);` — but a C# struct field
initializer only runs when an **explicitly declared** parameterless constructor is invoked; the implicit
struct constructor that `new counters()` would otherwise use zeroes every field and SKIPS initializers,
leaving the array's backing `T[]` null (an NPE on the first index or `len`). The `TypeGenerator`
therefore emits an explicit parameterless constructor for every struct, so `new S()` runs the field
initializers and each array field gets its `new(N)` backing. (C# 11 auto-defaults any field without an
initializer; a slice/map/chan field — which has no `new(N)` initializer — stays its nil zero value,
matching Go.) The **NilType constructor preserves the initializers too**: it used to re-assign
`this.field = default!` to every plain member — running *after* the field initializers, which nulled an
array field's fresh `new(N)` backing, so `S{}` (emitted `new S(nil)`) NREd on the first index. The
NilType and parameterless constructor bodies (`AppendZeroValueInitializers`) now assign only what C#'s
implicit zeroing would leave *broken*: the promoted-embed boxes (see
[Struct Type Embedding](#struct-type-embedding)), and — see next paragraph — any plain struct-typed field
whose own type needs construction; everything else is left to the field initializers plus C# 11
auto-default. This is generator-only and produces no golden churn (the `.g.cs` output is not a
golden). It is what lets `ArrayOfCrossPackageType` run as an **output-compared** test (`len(x.c)` /
`len(x.d)` print `3 2`); before the fix, indexing `&x.c[i]` threw a `NullReferenceException`, so the test
was compile+target-only.

A plain (non-embed) struct-typed FIELD whose type *itself* needs construction is the recursive case:
`default(T)` gives such a field a `default(FieldType)`, whose nested promoted-embed box or fixed-array
backing is null — so the first touch NREs even though `T`'s own boxes were constructed. This is fmt's
`pp{ … fmt fmt … }` where `fmt` embeds `fmtFlags` (a ctor-allocated box): `newPrinter`'s `@new<pp>()`
ran `pp()`'s ctor, which left `fmt` as `default(fmt)` with a null box, and `p.fmt.init(&p.buf)` →
`clearflags` NREd — the first crash of any converted `fmt.Println`. `AppendZeroValueInitializers`
therefore also emits `this.f = new FieldType(nil);` for each such field, and because that runs
`FieldType`'s own NilType constructor (which recursively constructs *its* needy fields), a single level of
construction fixes every depth. "Needs construction" (`StructTypeNeedsConstruction`) is: has a promoted
embed, a fixed-array field, or a nested struct field that needs construction; a reference field
(pointer/interface/delegate) keeps its correct nil zero value.

**The resolution must be by SYMBOL, not by syntax.** `StructTypeNeedsConstruction` originally answered
only for structs it could find a `StructDeclarationSyntax` for (`GetStructDeclaration`), and left every
other field type `default` on the reasoning that "its own package constructs it, and its nil zero value is
correct anyway". The first half is wrong and the second half does not apply to a fixed array. A
`<ProjectReference>` reaches the compiler as a **`PortableExecutableReference`** — compiled metadata with
no syntax trees — so in any real MSBuild build *every* cross-package field type was unresolvable, and
nothing in the consuming package ever constructed it. When such a type carries a fixed array at any depth,
`default` leaves that `array<T>`'s backing null (golib's deliberate zero-value discriminator), so `len` and
`range` silently measure **zero** and the first index or pin throws — `GCHandle.AddrOfPinnedObject`'s
`InvalidOperationException: Handle is not initialized` (see golib `ж.cs`, `pinnedArrayData`). The live case
was `math/rand/v2`'s `ChaCha8`, whose `internal chacha8rand.State state;` never got `State`'s
`buf = new(32)` / `seed = new(4)`, where Go's `new(ChaCha8)` yields 32 real zeroed words. (The syntax path
only ever worked because `CompilationReference`s — in-memory Roslyn compilations — do carry syntax; that is
the shape unit tests use, not the shape MSBuild produces.)

`GetStructDeclaration` is therefore backed by `Compilation.FindTypeSymbol`, which resolves a
fully-qualified display name to an `INamedTypeSymbol` through `GetTypeByMetadataName` (stripping `global::`
and verbatim `@`, rendering type arguments as arity suffixes, and trying each namespace-vs-nested-type split
of the dotted name since a display string spells both `.`). The metadata walk applies the same three
triggers over `GetMembers()` — a ref-returning property is a promoted embed, a `go.array<T>`-typed field is
a fixed array, a struct-typed field recurses — with the same cycle guard. On the metadata path the
`public T(NilType)` constructor that `new T(nil)` needs is **checked rather than assumed** (metadata is
fully compiled, so the generated constructor is really there): a hand-written golib struct or any other
referenced type without one returns `false` and correctly keeps its `default`. Scalars, pointers, slices,
maps and interfaces are still left `default`, because `default` **is** their Go zero value — over-
constructing would add an allocation to every instantiation for no semantic gain. (Guarded by the
`CrossPackageArrayZeroValue` output-compared test — a `Holder` whose field type lives in the `bufpkg`
sibling library sub-project, so the reference is genuinely metadata; a same-project field type resolves by
syntax and would pass even unfixed. Against the unfixed generator the test panics with
`index out of range [2] with length 0`.)

**The field-wise constructor closes the same gap for a PARTIAL composite literal.** The *parameterized*
constructor (`GenerateConstructor`, used by a composite literal that sets some fields —
`&Holder{tag: "lit"}` → `new Holder(tag: "lit")`) took `T f = default!` for every member and assigned
`this.f = f;` unconditionally, so an OMITTED needy-struct argument arrived as the broken `default(T)` and
overwrote the field — identical breakage to the NilType path above, and (because it never consulted
`StructTypeNeedsConstruction`) firing for a **same-package** field type too. The live case is `io.pipe`,
whose `onceError rerr, werr` value fields each embed `sync.Mutex` via the promotion box: `io.Pipe()`
builds the pipe with `new pipe(wrCh: …, rdCh: …, done: …)`, omitting `rerr`/`werr`, so both boxes were
null and the first `Store`/`Load` `Lock()` NREd on the pipe's writer goroutine — crashing every `io.Pipe`
consumer (`encoding/base32`'s `TestBufferedDecodingPadding`; the goroutine NRE aborted the whole test
host). A fixed-array member beside it already had the analogous `if (f.Source is not null)` guard (its
`= new(N)` field initializer supplies the omitted zero); the needy-struct member has no field initializer
to fall back on, so it must be *constructed*. `GenerateConstructor` now emits the needy value-struct
member's parameter as **nullable** — `onceError? rerr = default!` — making an omitted argument a genuine
`null` sentinel that `default(onceError)` (a real struct value with a null box) could never be — and its
body reconstructs only when omitted: `this.rerr = rerr ?? new onceError(nil);`, exactly mirroring the
pointer-embed `?? new ж<T>(nil)` handling for a promoted embed. A caller-SUPPLIED value is used as-is (no
extra allocation, unchanged reference semantics — the struct copy shares the same embed box); an omitted
one gets `T`'s own NilType ctor, which recursively constructs *its* needy members. The predicate
`IsNeedyValueStructMember` reuses `StructTypeNeedsConstruction` (member is not a promoted embed, not a
reference, not a fixed array, and its struct type needs construction), so every *ordinary* member's
parameter/assignment is byte-identical to before — the change is confined to genuinely-needy value-struct
fields. All of the NilType, parameterless, and now field-wise constructors are covered, so this reaches
`new(T)`/`@new<T>()`/`T{}`/`&T{…}` (empty **and** partial composite literals). (Guarded by the
`PromotedEmbedZeroValueField` output-compared test — a `slotBox{id: 3}` partial literal omitting a
`holder` value field that embeds a promoted `counter`, whose promoted `inc()` is then called on the
omitted field; against the unfixed generator it NREs with `Object reference not set to an instance of an
object`, exactly as `encoding/base32` did.)

A bare **`var x T`** zero-value declaration (no initializer) calls none of those, so the *converter*
closes the remaining gap on its side: when `T` needs construction it emits `T x = new();` — the generated
parameterless constructor, which runs the same field initializers + `AppendZeroValueInitializers` — instead
of the `T x = default!;` that left an array field's backing null (an NRE on the first index/`len`). The
converter mirrors `StructTypeNeedsConstruction` with the Go-side `structZeroValueNeedsConstruction` (promoted
embed / fixed-array field / nested needy struct, recursively; a reference field keeps its correct nil zero
value). A promoted-embed `var` keeps its existing `new(nil)` (the NilType ctor) and a scalar-only struct
keeps `default!`, so the change is confined to genuinely-needy structs — one pre-existing corpus golden
re-baselined, `PublicizedFieldType`'s `var cr CaseRange` (a `[3]rune` `Delta` field). A needy struct
**global** likewise gets `new()` in place of the bare `static T x;`. This `var`/global path is guarded by the
`ZeroValueStructVar` output-compared test (`var z holder` with a `[8]int` field, a nested `wrapper`, and a
scalar-only `point` control). Guarded by
the `NestedPromotedEmbedInit` output-compared test (a `printer` holding a `formatter` field that embeds
`flags` and holds a `[3]byte`, reached via both `new(printer)` and `&printer{}`, its promoted fields and
array written and printed against Go); before the fix the promoted-field write NRE'd.

#### Prefer the file-local package alias over the fully-qualified `_package` name

A cross-package named type has two C# spellings: the **fully-qualified** form `sync.atomic_package.Int32` (the namespace-rooted class, from `getFullyQualifiedTypeName`) and the **file-local alias** form `atomic.Int32` (the `using atomic = sync.atomic_package;` shorthand, from `getAliasQualifiedTypeName`). For *visual* fidelity — the converted C# should read like the Go original, which writes `atomic.Int32` — body emission prefers the alias. But the alias is only resolvable where the `using` is in scope, so the choice is made per emission site by `getScopeCheckedTypeName`, which returns the alias form **only when every cross-package type referenced by the type is imported in the current file** (checked against the per-file `importQueue`), and otherwise falls back to the fully-qualified form.

The fallback matters: a Go file may *index* an atomic-typed array field of a struct — `&x.c[i]` → `…at<E>(i)` — without ever naming the element type `E`, so it carries no `import "sync/atomic"` and no `using atomic`. There the element type must stay fully-qualified (it resolves inside `namespace go;` with no alias) or the file fails CS0246. When the package *is* imported (the common case, and every behavioral test of this), the prettier alias is used.

`getScopeCheckedTypeName` is applied at the body-emission sites that land in the current source file:

* **named struct-field declarations** — `internal atomic.Int64 total;` (was `sync.atomic_package.Int64`);
* **heap-box allocations** — `ref var n = ref heap(new atomic.Int32(), out var Ꮡn);`;
* **element-address `at<T>`** — `…at<atomic.Int32>(0)`.

It is **not** used for forms consumed by the source generators in alias-less generated files, which must stay fully-qualified: the `[GoType("…")]` attribute string (e.g. `[GoType("sync.atomic_package.Uint32")]`, `[GoType("[3]sync.atomic_package.Pointer<T>")]`), the `global using` type-alias declarations, and the promoted-interface/embedded-field registration keys. (Embedded fields keep the full form for their promoted accessors; only the named-field branch uses the display name. Struct-embedding promotion across packages re-derives member types from the Roslyn semantic model, not from the field's emitted text, so aliasing the field declaration is safe.) Guarded by `ArrayOfCrossPackageType`, `AtomicValues`, `FuncTypeParam`, `GenericAtomicPointerField`, `GlobalAtomicDefer`, `GlobalAtomicFieldMethod`, and `StructPromotionWithInterface`/`StructPointerPromotionWithInterface`.

#### Combined field-element address `base.at(field, i)`

The address of an element of an array/slice FIELD of a boxed value — `&x.c[i]` where `c` is an
array field, or the implicit address taken to call a pointer-receiver method `x.c[i].inc()` — was
rendered as a two-step chain `Ꮡx.of(counters.Ꮡc).at<atomic.Int32>(i)`: `of(field)` takes the field's
address (a `ж<array<E>>`), then `at<E>(i)` takes the element's. The explicit `<E>` is needed because
golib's standalone `at<TElem>(nint)` is generic in an element type unrelated to the pointer's `T`, so
it cannot be inferred. golib adds combined overloads `ж<T>.at<TElem>(FieldRefFunc<…array<TElem>…>, nint
index)` (one per field-accessor shape and array/slice kind, each forwarding to `of(field).at<TElem>(i)`)
whose `TElem` IS inferred from the field accessor's return type. The converter then collapses the chain
to `Ꮡx.at(counters.Ꮡc, i)` — dropping both the `.of(` step and the `<E>` type argument. It rewrites the
recursively-built field address `base.of(Type.Ꮡfield)` by retargeting its trailing `.of(field)` to
`.at(field, i)`, only when the field segment is parenthesis-free (a plain `Type.Ꮡfield` accessor, so the
final `)` provably matches the last `.of(`); any other shape falls back to the explicit chained form.
The combined overload is behaviorally identical to the chain (it literally forwards to it). (Guarded by
`ArrayOfCrossPackageType`, `IndexedElementDirectBoxMethod` and `PointerFieldArrayElementAddress` — all
output-compared; the `.inc()`/`bump()` element writes verify runtime equivalence.)

The routing gate sees through **nested value fields to the chain root**. `&pp.wbBuf.buf[0]` (runtime
`mwbbuf.go`) roots at the pointer `pp` through the *value* field `wbBuf`; the original gate checked
pointer-ness only one level up (`pp.wbBuf`, a struct), fell to a naive `Ꮡ` prefix (`Ꮡpp.wbBuf…` — CS1061
on the box), and the same failure hit the closure-captured variant (`&mp.trace.buf[gen%2]`, `trace.go`).
The gate now walks intermediate selectors to the root, so any pointer-rooted (or heap-boxed) chain routes
through the recursive `&field` machinery — `pp.of(pstate.ᏑwbBuf).at(wbBuf.Ꮡbuf, 0)` — which already
rendered multi-hop of-chains. A **nested-index** base — `&cache.entries[ck][i]` (2-D array via a pointer,
`symtab.go`) — is an `IndexExpr`, not a selector, so it gets its own arm: recursively take the inner
element's address (`cache.at(pcvalueCache.Ꮡentries, ck)`) and chain the outer `.at<T>(i)` onto it — the
gate also accepts a HEAP-BOXED value root (`&grid.cells[1][2]` on an address-escaping local), fixing that
shape too. An unboxed value-rooted chain keeps the prior naive form (corpus byte-identical). *Known
remaining gap (pre-existing): an intermediate `IndexExpr` inside the selector chain —
`&ptr.items[i].buf[j]`, an array-of-structs hop — defeats the root walk (both arms only step through
selectors) and keeps the CS1061 naive form; the recursive machinery likely has the pieces when a runtime
site demands it.* (Guarded by
the `NestedFieldElementAddr` behavioral test — all three runtime shapes with write-through vs Go; note a
ZERO-VALUED struct's array-field backing is null in the C# emulation — a separate pre-existing latent —
so the test initializes its arrays.)

**Element address of a by-value ARRAY PARAMETER.** Array parameters are cloned by value in the
function preamble (`value = value.Clone();`, Go's array-copy semantics) but are never
escape-analyzed, so they have no heap box — the naive element-address form would name a box that
does not exist (`Ꮡvalue.at<byte>(0)`, CS0103 — syscall `SetsockoptInet4Addr`, `&value[0]` on
`value [4]byte`). The converter boxes a **copy of the wrapper struct** instead:
`Ꮡ(value).at<byte>(0)`. `array<T>` wraps a `T[]` reference, so the copied wrapper SHARES element
storage with the cloned parameter — element reads and writes through the pointer stay behaviorally
correct. (One accepted edge, no stdlib hit: reassigning the *whole* array param after taking an
element address leaves the pointer on the older backing array.) (Guarded by `DeferTypelessReturns`'
`first` — element address of a `[4]byte` parameter, value vs Go.)

**Element address of a POINTER-to-array — `&t[i]` where `t` is `*[N]E`.** Go auto-derefs the index
(`(*t)[i]`), so the element lives in the pointed-to array on the heap; `t` already IS the `ж<[N]E>`
box. The converter emits `t.at<E>(i)` — ж's `at` materializes the array's lazy backing on the REAL
storage (a non-boxing constrained interface call) and then returns an element pointer over the shared
backing. The base is rendered in POINTER context so it yields the box: a deref-aliased pointer
PARAMETER gives `Ꮡt` (the parameter is `ж<[N]E> Ꮡt`, deref-aliased to `ref var t = ref Ꮡt.Value` in
the prologue), while a box-valued LOCAL from `new([N]E)` gives the plain `t`. Previously this shape
fell through every array/slice branch (the base's type is a `*types.Pointer`, not an array or slice)
to the generic `Ꮡ(t.Value[i])` **copy** form, which boxes a snapshot of the element and silently
drops any write made through the returned pointer. This is exactly hash/crc32's
`slicingMakeTable`/`simpleMakeTable`: `simplePopulateTable(poly, &t[0])` populated a throwaway copy,
leaving every CRC table all-zeros (checksums degenerated to `~0`-with-shifts — `TestGolden`,
`TestSlicing`). The same latent write-through-a-copy bug lurked corpus-wide wherever `&ptr[i]` on a
pointer-to-array was written through — `crypto/internal/nistec` (`p224GG[i].SetBytes(…)` in static
init), `internal/bisect` and `runtime` (`atomic.Store*(&arr[i], …)`) — all now alias correctly. (A
pointer-to-SLICE cannot reach here: Go does not auto-deref `*[]E` for indexing; it is written
`(*t)[i]`, a `StarExpr` the slice branch already aliases.) (Guarded by `PointerToArrayElementAddress`
— write-through `&g[j]` on a `*grid` local, value vs Go.)

**Element address of a SLICE FIELD of the receiver — `&b.lines[i]`.** The address of a slice element
uses one of two golib forms: the **element-aliasing** two-arg `Ꮡ(x, i)` (→ `new ж<T>(IArray, index)`,
whose `ValueSlot` returns `ref backingArray[index]`, so writes land in the shared backing array), or the
**copy-boxing** `Ꮡ(x[i])` (→ `Ꮡ(in T)`, which boxes a *copy* of the element value). For a slice the copy
form is only sound when nothing is written back through the pointer. Inside a pointer-receiver method the
converter had a `refRecv` fast-path that chose the copy form for a "receiver reference to a slice" — but
its detection keyed off `getIdentifier(indexExpr.X)`, which walks the selector chain to its **root**
identifier. So a slice *field* of the receiver — `&b.lines[i]`, whose base `b.lines` roots at the
receiver `b` — matched the fast-path too, and emitted the copy form. text/tabwriter's `terminateCell`
does `line := &b.lines[len-1]; *line = append(*line, cell)`: the `append` grew a *copy* of the row's
slice header and wrote the new length into the boxed copy, never back into `b.lines`, so every line
stayed length 0 and **all formatted output came out empty** (only the newlines survived). The fix
restricts the copy form to the case the receiver is *directly* the slice (`indexExpr.X` is the bare
receiver identifier); any slice base that is a field, call result, or other non-identifier expression
uses the element-aliasing `Ꮡ(x, i)` form — which is correct for a slice in all cases, since a slice value
always shares its backing array. This also corrected a benign read-only site (`NamedFuncTypeStructuralField`'s
`s.by(&s.items[j], &s.items[i])` comparison) from copy to alias. (Guarded by
`SliceFieldElementAddress` — append-through-pointer into a `[][]int` field of a pointer receiver plus an
in-place element mutate, value vs Go; validated end-to-end by `text/tabwriter`'s test suite.) The ARRAY
branch carried the identical defect and is narrowed the same way — next.

**Element address of an ARRAY FIELD of the receiver — `&d.hashHead[h]`.** The array branch had its own
`refRecv` fast-path with the same root-identifier detection, and so the same bug: an array *field* of the
receiver roots at the receiver and took the copy-boxing `Ꮡ(d.hashHead[h])`, whose `Ꮡ(in T)` overload
heap-boxes a copy of the **element**. `compress/flate`'s `deflate()` is the canonical victim — it does
`hh := &d.hashHead[hash&hashMask]; … *hh = uint32(d.index + d.hashOffset)` to maintain the chained hash
table. Every head write landed in a throwaway box, so `hashHead` stayed all-zero, `d.chainHead` was always
`0`, and the `d.chainHead-d.hashOffset >= minIndex` guard (`0-1 >= 0`) meant **`findMatch` was never
called at all**. Levels 2–9 therefore emitted LITERALS ONLY: still-valid deflate streams roughly the size
of `HuffmanOnly` output. Since `png.BestCompression` maps to flate level 9, a 256×256 PNG encoded to
**134,644 bytes instead of Go's 36,760** — pixel-identical on decode, ~3.7× weaker compression, with
`NoCompression`, `HuffmanOnly` and `BestSpeed` (whose `deflatefast.go` encoder writes `e.table[…]`
directly and never takes an element address) all byte-exact, which is what localized it. The fix mirrors
the slice branch: the copy form is kept only when the receiver is *directly* the array (`indexExpr.X` is
the bare receiver identifier); an array field of the receiver uses the element-aliasing two-arg
`Ꮡ(d.hashHead, (int)(…))`.

Note the array field deliberately does **not** route through the `.of(field)`/`.at<T>(i)` box machinery
described above, even though that machinery exists for array fields. Its trigger is `baseIsPointer` — the
*Go* receiver type is `*T` — but a Go pointer receiver renders as `this ref T recv`, which has **no** box
companion, so it emitted `Ꮡr.of(RegArgs.ᏑInts)` for `internal/abi`'s `&r.Ints[reg]` → CS0103. The two-arg
form needs no box and aliases correctly regardless: `array<T>` is a readonly struct wrapping an eagerly
allocated `T[]`, so evaluating the field copies only the wrapper while the copy shares element storage —
the same reasoning the array-*parameter* case above relies on. Seventeen corpus files corrected, several
of them silently broken in the same write-dropping way: `runtime`'s `&r.statusTraced[gen%3]`,
`&h.counts[…]` and `&m.stats[gen]` performed `.CompareAndSwap`/`.Store`/`.Add` **on a copy**;
`crypto/internal/edwards25519` built its lookup tables via `(&v.points[i]).FromP3(…)` into copies;
`image/jpeg` wrote Huffman/quantization tables through `&d.huff[tc][th]` and `&d.quant[…]`. (Guarded by
`RecvArrayFieldElementAddress` — chained-hash write-through with an unsigned index, plus a nested
`&h.pairs[i][j]`, value vs Go; the flate ratio itself is verified by deflating fixed buffers at every
level and byte-comparing the sizes against `go run`.)

### Array ASSIGNMENT copies the whole array (`.Clone()` on the RHS)

Go array assignment copies the array — `data := ints` yields independent storage — but the emitted
`array<T>` is a struct over a shared `T[]` backing store, so a plain C# struct copy **aliases**: a
write through the copy was visible through the source. The first operational hit was sort's
`TestReverseSortIntSlice` (`data := ints; data1 := ints` left ONE store sorted ascending then
descending, so the ascending/descending mirror check failed ×7 — misdiagnosed at first as an
embed-override dispatch defect; the dispatch was correct). The converter now appends golib's
strongly-typed `array<T>.Clone()` to an assignment RHS that copies an array **out of existing
storage** (see `cloneArrayValueCopy` in `visitAssignStmt.go`):

```go
d := garr        // → var d = garr.Clone();
var e = garr     // → array<nint> e = garr.Clone();
e = src          // → e = src.Clone();
f := h.arr       // selector RHS      → var f = h.arr.Clone();
row := m[1]      // index RHS         → var row = m[1].Clone();
g := *q          // deref RHS         → var g = q.Value.Clone();
x, y = y, x      // tuple swap        → (x, y) = (y.Clone(), x.Clone());
```

Only existing storage takes the clone — an ident, selector, index, or deref RHS reads a value some
other name can still reach; a composite literal, call result, or conversion is freshly constructed
and stays bare. The shape/type gate is the shared `exprReadsArrayValueFromStorage`
(`arrayCloneOperations.go`), which tests the UNDERLYING type — so direct, alias-declared, and NAMED
array types all clone (the named wrapper via its strongly-typed `Clone()`, next section), and an
interface-typed LHS (`var x any = arr`) boxes the clone. (Guarded by the `ArrayPassByValue`
extension — all seven assignment shapes above, written-through and read back against the source —
and `ArrayValueCopySites`' `namedAssignCopies` — named `:=`/`var`/`any`-boxed forms, values vs Go.)
The other copy sites of the same defect class — range elements, composite-literal elements,
returns, channel sends, append elements — are covered by the follow-up section below.

### Array VALUE-COPY at every transfer site (range, composite, return, send, append) — DEEP for nested arrays

Go copies the whole array at **every** value transfer, not just assignment and parameter passing —
but the emitted `array<T>` (and the generated named-array wrapper) is a struct over a shared `T[]`
backing store, so every plain C# struct copy ALIASES. The converter appends the strongly-typed
`.Clone()` wherever an array value is read **out of existing storage** (an ident, selector, index,
or pointer-deref — `exprReadsArrayValueFromStorage` in `arrayCloneOperations.go`; a composite
literal or call result is freshly constructed and needs none):

```go
for _, row := range m { row[0] = 99 }  // → foreach (var (_, vᴛ1) in m) { var row = vᴛ1.Clone(); … }
for i, row = range m { … }             // pre-existing vars → row = vᴛ1.Clone(); inside the body
m := [2][3]int{a, b}                   // → new array<nint>[]{a.Clone(), b.Clone()}.array()
s1 := holder{arr: a}                   // keyed struct field  → new holder(arr: a.Clone())
mv := map[string][3]int{"x": a}        // map value           → ["x"u8] = a.Clone()
mk := map[[2]int]string{k: "kv"}       // map KEY             → [k.Clone()] = "kv"u8
lst := []any{b}                        // interface boxing    → new any[]{b.Clone()}.slice()
return h.arr                           // return              → return h.arr.Clone();
ch <- a                                // channel send        → ch.ᐸꟷ(a.Clone())
s = append(s, a)                       // append element      → s = append(s, a.Clone())
```

The range emission routes an array-valued key/value through the same iterate-a-temp mechanism a
reassigned range var uses (a C# foreach variable cannot be redeclared from itself); a map range KEY
of array type clones the same way. The RECEIVE side of a channel needs no twin — the send stored an
unaliased element and a buffered element is dequeued exactly once.

Three deeper repairs make the single `.Clone()` correct everywhere:

- **`array<T>.Clone()` is DEEP for nested arrays** (golib `array.cs`): Go's `[2][3]int` copy copies
  the inner arrays too, but the shallow `T[]` clone left every nested backing shared — so even the
  cloned sites under-copied at depth ≥ 2 (`m.Clone()` then `m[0][0] = 99` wrote the source's inner
  array). An element that is itself an array wrapper (anything implementing `IArray`) is re-cloned
  through its `ICloneable` surface, which now returns the properly-wrapped clone so the unbox
  recurses through any nesting depth; the `typeof` gate keeps flat element types on the single
  shallow copy.
- **NAMED array types get a strongly-typed `Clone()`** (`IArrayTypeTemplate` /
  `IArrayViewTypeTemplate`): the wrapper's only clone was the object-returning `ICloneable` form, so
  named-array copies could not be expressed. `public Row Clone() => new Row(Value.Clone());` (and
  the view-wrapper equivalent through its underlying wrapper) lets every site above — plus the
  function-parameter preamble, func-literal parameters, and array-typed VALUE RECEIVERS — clone
  named and alias-declared arrays exactly like direct ones (`typeIsArrayValue` tests the underlying
  type, widening the old direct-`*types.Array` preamble gate).
- **`array<T>` equality/hash is structural per-ELEMENT** (golib `array.cs`): Go arrays are
  comparable values, so a `map[[2]int]V` key must be found again by an equal array with different
  backing. `GetHashCode` hashed the backing reference (every structural-equal key missed), and the
  `Equals` overloads passed container-typed comparers (`EqualityComparer<T[]>`) where
  `IStructuralEquatable` calls them per boxed element — throwing on the first equal-length
  distinct-backing comparison. Element-typed comparers fix both and recurse through nested arrays.

**The suffix must be WRAPPED on a `~`-prefixed rendering.** A deref whose operand is a pointer CAST
(`*(*T)(p)`, `convStarExpr`'s casted-pointer-deref path) renders with the PREFIX `~` operator, and C#
postfix binds tighter than unary — so a naked `.Clone()` re-binds onto the cast's inner operand
instead of the dereferenced array. reflect's `InterfaceData` is the real-world case:

```go
return *(*[2]uintptr)(v.ptr)   // reflect/value.go
```
```csharp
return ~(ж<array<uintptr>>)(uintptr)(v.ptr).Clone();    // WRONG — .Clone() reads v.ptr (CS1061)
return (~(ж<array<uintptr>>)(uintptr)(v.ptr)).Clone();  // emitted — clone the dereferenced array
```

Every clone-append site therefore routes its rendering through `appendArrayValueClone`
(`arrayCloneOperations.go`), which wraps only when the rendering starts with the deref operator —
the same precedence guard `convStarExpr` already applies when IT appends the postfix `.Value` to a
cast/deref rendering. Every other shape `exprReadsArrayValueFromStorage` admits (ident, selector,
index, and the postfix `.Value` deref form) is already a C# primary expression, so the change is
byte-neutral wherever the suffix was correct — the corpus-wide A/B footprint was exactly this one
reflect line, whose CS1061 had blocked the whole converted stdlib through `fmt`→`reflect`.

(All guarded by the `ArrayValueCopySites` behavioral test — one output-compared section per site
class, including multidimensional deep-copy through range and parameter passing — plus
`ArrayCastDerefClone`, which guards the wrapped cast-deref form above. That guard is now
**output-compared**: its TYPED-pointer half (`*(*T)(p)` where `p` is already `*T`) RUNS once the
identity reinterpret stops routing through the raw-address `uintptr` bridge (see *A SAME-TYPE
reinterpret … collapses to the pointer itself*), so the clone's copy semantics are proven by VALUE
— mutating the returned array must leave the pointed-to original untouched, and the lvalue form
must write through. Its `unsafe.Pointer` half stays compile-shape only, with the results
deliberately discarded: reconstructing an array through an `unsafe.Pointer` round trip reads raw
memory and cannot reproduce Go's values under the managed model.)

**Known remaining gaps (documented, not yet emitted):** (1) golib-internal
element-wise transfers of nested-array elements (`copy(dst, src)`, spread `append(dst, src...)`)
copy element structs without re-cloning; (2) an array-typed map KEY at an index-STORE (`mk[k] = v`
stores `k` uncloned — only the composite-literal key form clones); (3) a named↔underlying array
CONVERSION (`[4]int(named)`) hands the wrapper's backing through the implicit operator uncloned;
(4) an EMBEDDED struct member is held as a `ж<T>` box, so a struct copy shares the embed outright
(`b := a; b.n = 99` writes through to `a.n`) — a defect of the embed model, wider than arrays and
untouched by the section below.

### A STRUCT carrying array fields copies through its generated `ΔClone()`

The aliasing above is not confined to array-typed variables: a struct whose FIELD is a fixed-size
array has exactly the same problem one level up, because the plain C# struct copy carries the
field's `array<T>` header — and therefore its shared `T[]` — into the copy. crypto/sha256's `Sum` is
the canonical case; it copies the digest *precisely* so it can finalize the copy while the caller
keeps writing the original:

```go
type digest struct{ h [8]uint32; x [chunk]byte; nx int; len uint64; is224 bool }

func (d *digest) Sum(in []byte) []byte {
	d0 := *d                 // Go copies h and x INLINE
	hash := d0.checkSum()    // …then destroys d0's state finalizing it
	return append(in, hash[:]...)
}
```

With the copy sharing `h` and `x`, `checkSum` destroyed the CALLER's running state: every second
`Sum` on one hash returned a different digest, and `TestGolden`'s write-half → `Sum` → write-rest
sequence produced the hash of the empty string. sha1/sha256/sha512 all failed the same way, as did
`cryptotest.TestHash`'s SumAppend/ResetState/OutOfBoundsRead/StatefulWrite subtests.

The converter now treats such a struct exactly like an array. `typeNeedsValueClone`
(`arrayCloneOperations.go`) is the widened gate — a fixed-size array, **or** a struct carrying one
in a field, directly or through another such struct — so every site the array machinery already
covered (assignment/var-decl RHS, composite-literal element and keyed field, map key and value,
return, channel send, `append` element, range key/value, function/func-literal parameter and value
receiver) clones a struct too. The struct declaration is stamped with the fields that need it, and
go2cs-gen turns the stamp into the deep copy:

```csharp
[GoType] partial struct digest {
    internal array<uint32> h = new(8);
    internal array<byte> x = new(chunk);
    internal nint nx;
    internal uint64 len;
    internal bool is224;
}

[GoRecv] internal static slice<byte> Sum(this ref digest d, slice<byte> @in) {
    ref var d0 = ref heap<digest>(out var Ꮡd0);
    d0 = d.ΔClone();                 // was `d0 = d;` — the arrays were shared
    var hash = Ꮡd0.checkSum();
    …
}
```

```csharp
// generated (go2cs-gen StructTypeTemplate)
internal partial struct digest : IGoValueClone
{
    public digest ΔClone()
    {
        digest copy = this;
        copy.h = h.ΔClone();
        copy.x = x.ΔClone();
        return copy;
    }

    object ICloneable.Clone() => ΔClone();
}
```

Four details make this correct and collision-free:

- **The CONVERTER decides which fields clone, not the generator.** Only the converter has the Go
  type information, and it must agree with itself at the copy sites; `[GoValueClone("h", "x")]`
  (golib `GoValueCloneAttribute`) is that single source of truth. A defined type over such a struct
  (`type IpMaskString IpAddressString`, whose underlying holds a `[16]byte`) is emitted as go2cs-gen's
  inherited wrapper, so it is stamped `[GoValueClone("Value")]` and its clone forwards to that one
  member — without it, `syscall.IpAddrString`'s own clone had no `ΔClone` to call (CS1061).
- **The stamp is written on the `package_info.cs` accessibility record**, not on the declaration
  above — `[GoValueClone("h", "x")] internal partial struct digest {}` — so the converted source
  keeps the shape of the Go struct it came from. `TypeGenerator` reads it off any part of the
  partial type, which is also what lets a hand-owned conversion keep stamping it inline. See
  [Extended attributes](#extended-attributes-what-stays-on-the-declaration-and-what-moves).
- **The method is NOT named `Clone`.** A Go type may declare its own `Clone` method, which converts
  to an EXTENSION method on the package class — and an instance member of the same name silently
  SHADOWS it. Vendored `x/crypto/sha3`'s `func (d *state) Clone() ShakeHash` is the real case: its
  recv-overload forwarder bound to the generated `state Clone()` and failed CS0029. The name is
  `Symbols.ValueCloneMethod` (`ΔClone`), reusing the same `Δ` collision-avoidance marker the
  promoted-accessor rename uses. Copy SITES for plain arrays keep golib's public `Clone()`; only the
  generated bodies use the uniform name, which `array<T>` and the named-array/array-view wrappers
  alias to their own `Clone()` so one call form covers every clone-needing field type.
- **`array<T>.Clone()` recurses through the new marker.** It already re-cloned an element that is
  itself an array (`[2][3]int`); an element that is one of these structs (`[2]digest`) now clones the
  same way, through `IGoValueClone`/`ICloneable`.
- **EMBEDDED members are never listed and never cloned.** go2cs-gen holds an embed in a `ж<T>` box
  whose member accessor writes THROUGH the box, so assigning one in a clone would corrupt the
  source. Embedded-struct copy aliasing is the separate, pre-existing gap (4) above; this change
  neither fixes nor worsens it, and a struct that needs cloning only because of an embed is not
  stamped.

A BLANK or unnamed parameter is skipped: it is emitted under a synthetic name and can never be
referenced, so there is nothing for the copy to protect — and the preamble would otherwise be
written against the empty analyzed name (` = .ΔClone();`, CS1525 ×2 in `log/slog`'s benchmark
`Handle(disabledHandler, context.Context, slog.Record)`, every parameter of which is blank). The
array-typed arm had the same latent hole; no blank array parameter happened to exist in the corpus.

(Guarded by the `StructArrayFieldValueCopy` behavioral test — one output-compared line per site
class: pointer-deref copy, ident copy, selector copy, composite-literal element, nested-struct copy,
by-value parameter, returned field, array and slice index, range value, map value, value receiver.
Validated end-to-end by crypto/sha1, crypto/sha256, crypto/sha512 and bufio, whose only residue is
the `alloc-profile` disclosure the extra managed allocations force.)

### Nil-vs-empty slice identity (`s == nil` is representation nilness, not emptiness)
Go distinguishes a **nil** slice (nil backing pointer) from a **non-nil empty** slice (a real backing
pointer with zero length), and programs observe the difference through `s == nil`, `reflect.DeepEqual`,
and marshaling — bytes' `TestTrim`/`TestTrimFunc`/`TestClone` assert it directly (`TrimRight` of a
non-empty slice trims *in place* and stays non-nil; `Clone` of a non-nil input must return non-nil;
the `[]byte{}` *want*-side literals must not classify as nil). golib `slice<T>` carries the
distinction in its representation — the backing `m_array` field is `null` exactly for the nil slice —
but the observation and two construction paths used to lose it:

- **`operator ==(slice<T>, NilType)`** tested `Length == 0 && Capacity == 0`, misclassifying every
  zero-length zero-capacity view (`[]byte{}`, `[]byte("")`, `x[len(x):]`) as nil. It now tests
  `m_array is null` — representation nilness.
- **`operator ==(slice<T>, slice<T>)`** was structural content equality. Go forbids comparing two
  slices, so the *only* converted code binding this operator is the nil comparison `s == nil`, which
  renders as `s == default!` (the nil literal renders `default!` in value contexts). It is now Go
  slice-**header identity** (same backing array reference, offset, length, capacity), which against
  the default header `(null, 0, 0, 0)` is exactly the nil test. Structural equality remains on the
  `Equals` overloads for C#-side collection use, and the generated named-type wrappers bind `Equals`
  (not this operator), so their behavior is unchanged.
- **`Reslice`** (every `s[a:b]`/`s[a:b:c]`) laundered a nil backing into a fresh empty array
  (`m_array ?? []`). Reslicing nil is legal only within zero bounds, and Go's `nil[0:0]` **is** the
  nil slice (the result shares the nil backing pointer) — it now returns `default` for a nil source.
- **`Append`** with **zero elements** allocated a fresh empty slice for a nil source. Go's
  `append(s)` (and `append(s, empty...)`) returns `s` itself — no growth is needed, so the same
  header comes back: nil stays nil, and bytes.Clone's `append([]byte{}, b...)` with empty `b`
  returns the non-nil literal. `Append` now returns the source unchanged when there is nothing to
  add. (`builtin.widen`, the generic pointer-instantiation projection, likewise now projects only a
  nil source to nil instead of any empty one.)

The full identity enumeration golib maintains (invariant: **nil ⟺ `m_array is null`**):

| Construction | Go identity | golib path |
|---|---|---|
| `var s []T`, struct/element zero values | nil | `default(slice<T>)` — null backing |
| `[]T(nil)`, nil literal in slice context | nil | `T[]`-taking ctors map null → `default` |
| `nil[0:0]`, `nil[0:0:0]` | nil | `Reslice`/bounded ctors preserve the null backing |
| `append(nilSlice)` — nothing to add | nil | `Append` returns the source header unchanged |
| `[]T{}` composite literal | non-nil empty | `new T[]{}.slice()` — real empty array |
| `[]byte("")` / `[]rune("")` / conversions of empty strings | non-nil empty | span/`@string` paths materialize a real array |
| `make([]T, 0)` | non-nil empty | parameterless ctor / `Make` — real empty array |
| `s[a:a]`, `s[len(s):]` of non-nil `s` (even cap 0) | non-nil empty | `Reslice` shares the real backing array |
| `append(emptySlice)` — nothing to add | that same non-nil empty | `Append` identity return |
| `append(s, elems...)` with elements | non-nil | in-place or reallocated — always a real array |

Known adjacent gap, deliberately out of this change's scope: a **zero-argument variadic call**
materializes a non-nil empty (`params Span<T>` → `.slice()`) where Go passes nil. (Guarded
by the `SliceNilVsEmpty` behavioral test — every row of the table probed with `s == nil`, `len`, and
`cap` against `go run`; `resliceTailCapZero` discriminates the operator fix, `nilReslice` the
`Reslice` fix, and `appendNilNothing` the `Append` fix. `NilSliceConversion` continues to guard the
`[]T(nil)` conversion row.)

**Named slice/map/channel wrappers (defined types).** The distinction extends to DEFINED types
(`type S []int`, generated as go2cs-gen `InheritedTypeTemplate` wrappers). The comparison Go permits on
a named slice/map/channel is `x == nil`; the converter renders it as `s == default!` (nil literal in
value context) or `s == nil` (pointer context), and — verified against the emitted C# — **both bind the
wrapper's `operator ==(S, NilType)` overload**, not the same-type `operator ==(S, S)` (reverting the
latter has no observable effect; reverting the former flips the result). That overload emitted
`value.Equals(default(S))`, and the **slice** wrapper's `Equals` (from `ISliceTypeTemplate`'s
`Equals(ISlice<T>?)`) is structural *content* equality, so an empty non-nil named slice (`S{}`,
`make(S, 0)`, an `s[len(s):]` tail) was misclassified as nil. It now delegates to `slice<T>`'s own
`== NilType` — REPRESENTATION nilness (null backing array, R13) — via `value.m_value == nil`, so
`IntSlice{} == nil` is false while the zero value stays nil. Audit of the other nil-comparable kinds:
**map** and **channel** wrappers were already correct and are unchanged — they declare no structural
`Equals`, so `Equals(default)` falls back to reference identity through the backing field
(`map<K,V>.Equals` is `ReferenceEquals(m_map, …)`; `channel<T>` compares its queue by reference).
Array/numeric/string/struct/`any` wrappers are not nil-comparable in Go (and the pointer wrapper
already uses a reference-identity `Equals` override), so they keep the structural default; the same-type
`operator ==(S, S)` is likewise left untouched (Go forbids comparing two slices, so no converted code
reaches it). Because this is a compile-time (go2cs-gen) change it leaves TRANSPILER output — the
`.cs.target` goldens — byte-identical, so it is gated on the FULL behavioral suite (four phases)
**plus** a full corpus build rather than CNR alone.

Separately, `NilType`'s `operator ==(ISlice?, NilType)` dropped its historical
`{ Length: 0, Capacity: 0, Source: null }` arm: `slice<T>.Source` (and every wrapper's
`IArray.Source`) materializes a DETACHED copy (`ToSpan().ToArray()`) and is never null, so the
property pattern could never match — the expression already reduced to `slice is null`
(representation nilness), which is what it now states plainly. No converted `s == nil` routes through
this interface operator: a concrete `slice<T>` binds its own `== NilType`, and interface/`any`
comparands bind `NilType`'s `object` arm.

(The named-wrapper rows are guarded by the `NamedSliceNilVsEmpty` behavioral test — named
slice/map/channel zero value (nil) vs empty literal (non-nil), plus the slice `resliceTailCapZero` and
`nilReslice` discriminators, output-compared vs `go run`; it fails if the wrapper's `== nil` regresses
to structural equality.)

### A composite literal omitting a fixed-array field keeps the zeroed backing

A Go struct's fixed-array field is emitted with a field initializer carrying its Go length
(`badCharSkip [256]int` → `internal array<nint> badCharSkip = new(256);`), and C# runs field
initializers in every explicitly declared constructor — but the generated **parameterized**
constructor then assigned every member from its argument, and an argument the composite literal
OMITS arrives as the zero value `default!`, whose backing `T[]` is null. The assignment nulled the
initializer's backing, so the field's first walk NREd (strings' Boyer-Moore
`stringFinder{pattern: …, goodSuffixSkip: …}` never sets `badCharSkip` — the
`TestFinderCreation`/`TestFinderNext` operational blocker, Phase-4 row R8). The generated
constructor now guards exactly the fixed-array members:

```csharp
if (badCharSkip.Source is not null) this.badCharSkip = badCharSkip;
```

`array<T>.Source` intentionally returns the RAW backing reference (null discriminates a
never-constructed zero value), and keeping the initializer for a zero-value argument is precisely
Go's semantics — the zero `[N]T` IS the zeroed backing the initializer produced. A constructed
argument assigns as before (see the copy-semantics gaps above for the literal-argument clone).
Separately, golib `array<T>` reads are now **null-safe**: a bare `default(array<T>)` (a zero value
no constructor ever touched) enumerates/compares/prints as an EMPTY array and panics Go-style on
any index, instead of throwing NRE — mirroring `@string`'s null-safe zero value. The empty view is
a disclosed approximation: the declared length only exists where a constructor or initializer ran,
so a `holder z = default!;` zero-var local still reads its array field at length 0, not N (a known
converter gap, chipped separately; the `make([]S, n)` half of it is now closed — see *`make([]E, n)`
constructs its ELEMENTS by the same rule* below). (Guarded by the
`ZeroValueArrayField` behavioral test — the literal-omission shape ranged/indexed/printed vs Go,
plus an explicit-argument control.)

### A fixed-array composite literal carries its DECLARED length (`.array(N)`)

A `[N]T{…}` literal is **N** long however many elements it writes — Go zero-fills the rest, so
`[8]byte{}` is eight zero bytes and `[8]byte{1, 2}` is `1, 2` followed by six zeros. The literal
renders as a C# element array projected through golib's `.array()` extension, and that element
array holds only the elements actually written, so the projection produced an array as long as the
LITERAL rather than as long as the TYPE. `[8]byte{}` became length **0**: it compiled cleanly and
then panicked on first use (`index out of range [7] with length 0` — math/rand/v2 `chacha8`'s
`Seed`, whose `[8]byte{}` never held a byte). The projection now takes the declared length:

```go
a := [8]byte{}          // eight zero bytes
b := [8]byte{1, 2}      // 1, 2, then six zeros
c := [3]byte{1, 2, 3}   // already full
```

```csharp
var a = new byte[]{}.array(8);
var b = new byte[]{1, 2}.array(8);
var c = new byte[]{1, 2, 3}.array();      // full literal keeps the plain projection
```

Only a **short** literal takes the length argument. A full literal — and every `[...]T{…}`
ellipsis literal, whose length *is* its element count — already yields the right length and keeps
the plain `.array()` form, so existing goldens for those are unchanged. A **slice** literal is
genuinely as long as its elements (`[]byte{}` IS empty) and never pads; its `.slice()` projection
is untouched. golib's `array<T>(T[] source, int length)` constructor does the zero-filled copy,
which is deliberately distinct from the `array(slice<T>, nint)` slice-to-array *conversion* ctor
(there a short source is a Go panic; here it is the normal case).

The same dropped length reached the **indexed/keyed** form by a second route. A keyed literal
whose indices all fold to constants renders as `new array<T>(N){[i] = v}`, which was already
correct — but the scan used `0` as its "no constant keys" sentinel, so a literal whose only key
*is* 0 (`[8]byte{0: 9}`) read as unresolved and fell to the `SparseArray` projection, whose extent
is `max index + 1`, not `N`. Constant-key detection is now tracked separately from the maximum
index, and the `SparseArray` projection — still used for a key that is constant but not a literal
(a `const` identifier), which `SparseArrayIfaceElem`'s `[kLast]shape` registry exercises — also
carries the declared length. (Guarded by the `ArrayLiteralDeclaredLength` behavioral test: empty,
partial, full, ellipsis, keyed, zero-keyed, named, aliased, package-level, non-byte element types,
a tail write proving the backing is really N long, and a `[]byte{}` slice control, output-compared
vs `go run`; the pre-fix converter exits with the index-out-of-range panic. Note a NESTED fixed
array's inner elements are still default-constructed — `[2][4]byte{}` gets the right outer length
but inner length 0, and so does every element the padding itself creates, so
`[2][4]byte{{1, 2, 3, 4}}` reads inner `4` then `0`. The `var` DECLARATION path is fixed by the
element factory described in the next section, but `convCompositeLit` does not yet use it, so the
LITERAL path stays open — chipped separately.)

### A fixed-size array constructs its ELEMENTS when `default(T)` is not usable storage

`new array<T>(N)` fills its backing with `default(T)`, which is the correct Go zero value only when
`default(T)` is itself well formed. For a NESTED fixed array it is not: `[2][4]byte` emits
`array<array<byte>>`, and the inner length `4` lives only in the Go type — `array<T>` has nowhere to
carry it, so golib cannot recover it from `T`. Every element kept a null backing, so `len(x[1])`
reported 0 where Go says 4, and the first indexed write panicked (`index out of range [2] with
length 0`) — a silent-correctness defect that compiled clean. The same held for an element whose own
zero value needs construction: `default(T)` skips the generated constructor that runs a struct's
fixed-array field initializers and allocates its embed boxes.

Only the converter knows the element's shape, so it supplies an element factory to a golib
`array(int length, Func<T> elementFactory)` constructor:

```go
var x [2][4]byte           // len(x), len(x[1]) => 2 4
var deep [2][3][4]byte
var se [2]inner            // type inner struct { b [3]byte }
```
```csharp
array<array<byte>> x = new(2, () => new(4));
array<array<array<byte>>> deep = new(2, () => new(3, () => new(4)));
array<inner> se = new(2, () => new());
```

The factory nests to any depth, and each element gets its OWN storage rather than one shared inner
array. It is emitted from every fixed-array zero-value site — local `var`, package-level `var`
(including the addressed-global `ж<>` box form), the type-ALIAS-to-array spelling, a struct's
field initializer (`internal array<array<nint>> entries = new(2, () => new(3));`), the
**heap-boxed** (address-taken) local, and the **`new([N]T)`** builtin:

```go
var leafCounts [maxBitsLimit][maxBitsLimit]int32   // addressed: copy(leafCounts[i][:i], …)
f.bits = new([maxNumLit + maxNumDist]int)
```
```csharp
ref var leafCounts = ref heap(new array<array<int32>>(16, () => new(16)), out var ᏑleafCounts);
f.bits = Ꮡ(new array<nint>(316));
```

Those last two were the same silent-correctness defect one layer down. The heap-boxed declaration is
a THIRD emission path (`convertToHeapTypeDecl`, a string path that never consulted
`arrayZeroValueArgs`), and it is exactly the shape `compress/flate`'s Huffman coder uses —
`bitCounts`' `leafCounts [16][16]int32` is boxed because `copy(leafCounts[i][:i], …)` slices an
element, and its first `leafCounts[level][level] = 2` panicked with `index out of range [1] with
length 0`, taking `compress/gzip` and `compress/zlib` down with it. `new([N]T)` is a FOURTH: golib's
`@new<T>()` builds the zero value through the parameterless constructor, where `array<T>()` has no
length at all, so `f.bits` came back length 0 (Go: 316). A NAMED array type keeps the zero-value
`@new<row>()` form for the same reason a named element needs no factory.

A NAMED array element needs no factory and is deliberately left alone: `type row [4]byte` generates
a wrapper that allocates its backing lazily from its own known size (go2cs-gen's
`m_value ??= new row(4)`), so `array<row> nr = new(2);` is already correct. Elements whose
`default(T)` is a valid zero value (scalars, pointers, slices, maps) likewise keep the bare
`new(N)`, which keeps the A/B footprint to genuinely nested shapes.

This mirrors go2cs-gen's `AppendZeroValueInitializers`/`NeedsConstruction`, which does the same for
struct FIELDS, and narrows the zero-value gap disclosed above — a `default!` zero-var local still
reads an array field at length 0. (Guarded by the `NestedFixedArrays` behavioral test: inner `len`,
writes read back through inner arrays, per-element storage independence, three-level nesting,
struct/named-array elements, and the global paths, all compared against `go run`.)

### `make([]E, n)` constructs its ELEMENTS by the same rule

`slice<T>`'s length constructor fills its backing with `default(T)` exactly as `array<T>`'s does, so
the identical silent-correctness defect reached `make`. `make([][hashSize]int, n)` emitted
`new slice<array<nint>>(n)` and produced n **zero-length** arrays, because the inner length lives
only in the Go type — so hash/maphash's `avalancheTest1` panicked on its first `g[j] += …`
(`index out of range [0] with length 0`), and `image/draw`'s Floyd-Steinberg `quantErrorCurr`/`Next`
rows and `x/text/transform`'s chain buffers carried the same latent defect unexercised.

`make` now threads the **same** `arrayElemFactory` the fixed-array zero-value sites use into a golib
`slice(nint length, Func<T> elementFactory, nint capacity = -1, nint low = 0)` constructor — one
rule, one predicate, both containers:

```go
grid := make([][hashSize]int, n)          // hash/maphash smhasher_test.go
q := make([][4]int32, r.Dx()+2, cap)      // image/draw
```
```csharp
var grid = new slice<array<nint>>(n, () => new(64));
var q = new slice<array<int32>>(r.Dx() + 2, () => new(4), cap);
```

The factory fills the **whole backing**, not just the first `length` elements: Go zeroes the entire
allocation, so the capacity beyond the length is already valid storage once a re-slice or `append`
exposes it. As with `array<T>`, a NAMED array element (`type row [4]byte`) and every element whose
`default(T)` is a valid zero value keep the plain length constructor, so the A/B footprint stays on
genuinely nested shapes. The composite-literal path (`[][4]int{{…}}`) is still open, chipped
separately with the array-literal case above. (Guarded by `NestedFixedArrays`, extended with the
`make` length and length+capacity forms, a struct element needing construction, and a named-array
element control, output-compared vs `go run`.)

**A DEFINED slice type routes the factory through its underlying `slice<E>`** (2026-07-27). `make`'s
target is not always `slice<E>`: for `type SortedMap []KeyValue` the target is the go2cs-gen wrapper,
which declares `SortedMap(nint length, nint capacity = -1, nint low = 0)` and **no element-factory
overload**, so the lambda bound to `nint` — `CS1660: Cannot convert lambda expression to type 'nint'`
at `internal/fmtsort`'s `make(SortedMap, 0, n)`. The factory-filled backing is therefore built as the
underlying `slice<E>` — bit-for-bit the value the unnamed form produces — and handed to the wrapper's
`T(slice<E> value)` constructor, which the generator always emits:

```go
sorted := make(SortedMap, 0, n)           // internal/fmtsort sort.go
```
```csharp
var sorted = new SortedMap(new slice<KeyValue>(0, () => new(), n));
```

This was a **live** defect, not a latent one, and its blast radius is worth recording: `-tests`
regenerates production `.cs` on every run, so once the run for one banked package left a broken
`sort.cs` on disk, every later package downstream of `fmt` failed to build too — a validated sweep
read as 41 pass / 20 fail from this single root. It is the fourth instance of the zero-value-
construction class in a fourth emission path, which is the standing argument for centralizing that
construction rather than patching sites.

### `clear` rebuilds each element through golib's `GoZero` — the RUN-TIME half of zero-value construction

The fifth instance of that class landed in golib rather than the converter, and it is the one that
made the centralization real. `builtin.clear(slice<T>)` assigned `default!` to every element, so
`clear(q)` over a `[][4]int32` replaced each element with a **length-zero** array — Go's
`clear` leaves four zeroed `int32`s. `image/draw`'s Floyd–Steinberg dither calls
`clear(quantErrorNext)` once per scan line, and the next row's `quantErrorNext[x][0]` panicked with
`index out of range [0] with length 0`. The same held one level in for a struct element carrying a
fixed-array field: `default!` skips the generated parameterless constructor that runs its field
initializers.

The converter cannot help here — a `clear` call site has no Go type shape to thread a factory
through, and the C# type `array<E>` is the same for every N. Patching the site would have been the
fifth per-site repair of one defect, so the shape recovery moved into golib and became a single
entry point:

```csharp
public static T GoZero<T>(T template)   // builtin.cs
```

`GoZero` returns the Go zero value of `T`, consulting `template` **only for run-time shape**, and
resolves one of three answers from a per-closed-`T` static (`ZeroFacts<T>`, the same JIT-folding
shape as `AssertFacts<T>`), so the overwhelmingly common case compiles to a constant `default`:

| `T` | zero |
|:--|:--|
| any reference type, or a value type golib owns (`@string`, `slice<T>`, `map<K,V>`, the numerics) | `default` |
| `array<E>` — implements the new golib marker `IGoZeroShaped` | `GoZeroLike()`: a new array of the template's LENGTH, elements zeroed recursively so `[2][3]int32` keeps its inner lengths |
| a converted Go struct (`[GoType]` + a generated parameterless constructor) | that constructor — exactly what the converter emits for `var x T`, and `default` for a plain struct |

All three slice-shaped `clear` overloads (`slice<T>`, `Span<T>`, and the constrained `ISlice<T>`)
route through one `Span<T>` body that keeps the vectorized `Span.Clear()` whenever
`default(T)` is already the Go zero value:

```go
q := make([][4]int32, 3)
q[1][2] = 7
clear(q)
len(q[1]) // 4, not 0
```

The `[GoType]`-plus-constructor rule is deliberately broad rather than a per-shape enumeration
(fixed-array field, promoted embed, …): calling a converted struct's own zero-value constructor is
always correct, so a FUTURE field shape that needs construction is covered without re-opening the
class a sixth time. That generality is the whole point — this is the run-time counterpart of the
converter's `arrayZeroValueArgs` and go2cs-gen's `AppendZeroValueInitializers`, which build a zero
value where the shape is known statically; `GoZero` recovers it from a value that already carries
it, which is what a built-in is handed. (Guarded by the `ClearBuiltinShadow` behavioral test,
extended with `clear` over an array-element slice, a struct-with-array-field slice, and an
array-of-arrays element, each written to after the clear and output-compared vs `go run`.)

### A named slice wrapper's non-generic `ISlice.Append` is an EXPLICIT implementation

The generated wrapper for `type S []E` implements both halves of the golib slice surface, and both
declare an `Append`: the typed `ISlice<E>.Append(E[])` and the non-generic `ISlice.Append(object[])`.
`ISliceTypeTemplate` emitted both **public**, which is fine for every `E` except one — with
`E = any`, `object[]` and `E[]` are the SAME parameter list, so the wrapper carried two public
methods differing only in return type: **CS0111**. That is a single duplicate-member emission, and
it held two whole converted test suites, `fmt`'s `type SE []any` (63 verdicts) and `archive/tar`'s
`type fileOps []any` (97) — a `[]any` named slice is a table-driven-test idiom, which is why the
production corpus never met it.

The non-generic overload is now explicit —

```csharp
ISlice? ISlice.Append(object[] elems) => ((ISlice)m_value).Append(elems);
```

— which is what golib's own `slice<T>` has always declared (`ISlice ISlice.Append(object[] elems)`
beside `ISlice<T> ISlice<T>.Append(params T[] elems)`), so the wrapper now matches the type it
wraps. The reasoning is the same one the template already applies to `GetEnumerator` in the
subsection above: this is the boxing, interface-typed path, taken only when a consumer asks for the
interface, and the public surface is the typed overload. Converted code never calls it by name —
Go's `append` emits golib's `append` builtin, which reaches `slice<T>.Append` statically.

Measured after: both suites clear this root and stop on unrelated ones — `fmt` on five
(CS1955 `map` used as a method, CS0030 on renamed complex types, CS1729/CS0103/CS0034 around
`Scan_type`), `archive/tar` on the duplicate `global using` alias its board row records as closed
and which is in fact still live. Neither banks. Guarded by the `NamedAnySliceType` behavioral test —
both suites' declarations verbatim, spread into a variadic `...any`, appended to, indexed, ranged,
sub-sliced, spread into a second named `[]any`, and compared against `nil`, output-compared vs
`go run`.

## Strings (`@string` and `sstring`)
Go's `string` is represented by golib [`@string`](https://github.com/ritchiecarroll/go2cs/blob/master/src/core/golib/string.cs), not `System.String`. That is a semantic decision, not just a naming one: Go strings are immutable byte sequences, so `len`, indexing, ranging, concatenation, conversion to `[]byte`/`[]rune`, equality, and type assertions must all observe Go's UTF-8/byte model rather than C#'s UTF-16 string model. A zero-value `@string` is also null-safe and reads as `""`, which lets `default!` stand in for Go's zero value without sprinkling null checks through converted code.

Plain Go string literals usually render as C# UTF-8 literals (`"..."u8`, a `ReadOnlySpan<byte>`) and are target-typed only at the boundary that needs an actual Go string. That gives allocation-free fast paths such as `[]byte("hi")` -> `slice<byte>("hi"u8)`, `@string s = "hi"u8`, and comparisons against `sstring` views. When the literal's bytes cannot be represented faithfully as source UTF-8 -- notably high `\xHH` escapes and greedy hex-escape runs -- the converter emits a byte-array-backed `@string` instead, so byte indexing and `len` stay Go-correct.

Named string types are generated as real `[GoType("@string")]` wrappers. The generator supplies the Go string surface directly on the wrapper -- byte indexers, range/sub-slice behavior, `Length` for `len`, `ReadOnlySpan<byte>` bridging for `u8` literals, comparisons, and conversions through the underlying `@string` -- so code that declares `type Token string` keeps distinct-type behavior while still reading like a string in method bodies.

The heap `@string` form is always the correctness fallback for `string([]byte)`: it copies bytes into an immutable string, matching Go when the value escapes or the source buffer can later mutate. The performance fast path is golib [`sstring`](https://github.com/ritchiecarroll/go2cs/blob/master/src/core/golib/sstring.cs), a stack-only `readonly ref struct` view over a `ReadOnlySpan<byte>`. A local or expression-level `string([]byte)` conversion may emit `sstring` only when the converter can prove the view is read-only, non-escaping, and not observed after a source mutation; if that proof is too weak, the conversion stays `@string`. Because `sstring` is a `ref struct`, most missed escape cases are C# compile errors rather than silent aliasing bugs.

A **built-in used as a generic type argument** is rendered in its golib form, the same as anywhere else — in particular Go `string` becomes golib `@string`, never C# `string` (`System.String`). This matters because the converter adds a `new()` constraint to every generic type parameter: `@string` is a struct with a public parameterless constructor and satisfies it, whereas `System.String` would violate it (CS0310), and assigning a string literal — emitted as a `u8` `ReadOnlySpan<byte>` — into such a field would fail (CS0029). So:

```go
type Pair[A any, B any] struct { a A; b B }
var p Pair[int, string]
p.b = "hi"
```
```csharp
Pair<nint, @string> p = default!;
p.b = "hi"u8;
```

This applies uniformly to every type-argument position — first, second-or-later, and nested (`Pair[int, Box[string]]` → `Pair<nint, Box<@string>>`). (The behavioral test `GenericStringTypeArg` guards these cases; `NestedGenericTypes` covers the nesting depth without string args.)

### Slicing a string is a WINDOW, not a copy — `@string` carries an offset and a length

A Go string header is a *pointer plus length* into shared immutable storage, which is what makes `s[i:j]` an **O(1)** operation that allocates nothing. `@string` originally held a bare `byte[]`, so its range indexer had to materialize the sub-string's bytes: `s[i:]` was **O(n) with an allocation**. That is invisible in the small and quadratic in the ordinary Go idiom for walking a string by runes —

```go
for i := 0; i < len(s); {
    r, size := utf8.DecodeRuneInString(s[i:])
    i += size
}
```

— which is exactly what `archive/zip`'s `detectUTF8` does over every file name and comment. At the 65,535-byte names its `TestZip64LargeDirectory` builds, each call copied ~2.1 GB; the test takes 13.2 s in Go and had not finished in **45 minutes** in C#.

`@string` therefore carries the Go header's shape — a backing array **plus an offset and a length** — and its range indexer returns a window over the same array. Slicing a string now allocates nothing and copies nothing, matching Go's cost model. The same test completes in 20.2 s against Go's 11.3 s, and `archive/zip` validates 98/98.

Sharing the backing array is safe for precisely the reason it is safe in Go: **`@string` is immutable**, and every conversion *out* to storage the receiver may mutate — `[]byte(s)`, the `byte[]` operator — already copies (see the next section, and the `unicode/utf8` `TestDecodeRune` corruption that pinned those copies down). Two consequences worth carrying:

- The backing array is **private** to `@string`. A consumer reading it directly instead of reading the window would silently see the *whole* backing rather than the string, so privacy makes that a compile error rather than a wrong answer — which is how the remaining raw-array readers (`sstring`'s mixed comparison/concat operators, `builtin.slice(@string,…)`, `ByteSeqExtensions.ToGoString`) were found and corrected. Bounds that were measured against the backing array are now measured against the window (`@string.SliceBounds`) — the same correction `array<T>.slice` already carried for an alias window.
- `unsafe.StringData` pins a window that does not begin at the backing array's start by materializing its bytes first, since a `GCHandle` pins an object from its start. Whole-backing strings — the overwhelming majority, and every string that reached there before windows existed — pin in place unchanged.

### Converting a string to `[]byte` / `[]rune`
A Go `[]byte(s)` / `[]rune(s)` element-decoding conversion is emitted as the golib element-slice form `slice<byte>(…)` / `slice<rune>(…)`, which relies on the `@string`→`slice<byte>`/`slice<rune>` conversion. When the source is a string **variable** it is already golib `@string`, so the conversion applies directly. When the source is a bare string **literal**, that literal would otherwise render as a `System.String` (no such conversion exists — CS1503/CS1929), so the converter casts it to `@string` first:

```go
bs := []byte("hello")
rs := []rune("héllo")
```
```csharp
var bs = slice<byte>((@string)"hello");
var rs = slice<rune>((@string)"héllo");
```

The `@string` cast fires only on a string-literal argument; a string-variable conversion (`[]byte(s)`) needs no cast. (Guarded by the behavioral test `StringLiteralSliceConversion`.)

The cast reaches a *top-level* literal argument only, which left Go's line-**splitting** literal idiom — one constant string written as several `+`-joined pieces so it fits the source width — rendering as a bare C# `string` concatenation. C# will not chain the two user-defined conversions `string` → `@string` → `byte[]` that golib's `slice<T>(T[])` would need, so `crypto/hmac`'s long-key vectors failed `CS1503: cannot convert from 'string' to 'byte[]'`. A `[]byte`/`[]rune` conversion whose argument is a **constant-valued binary expression** therefore casts the rendered operand as a whole, which keeps the source's split verbatim (C# constant-folds the concatenation itself, so preserving it costs nothing at run time):

```go
key := []byte("This is a test using a larger than block-size key " +
    "and a larger than block-size data. The key needs to " +
    "be hashed before being used by the HMAC algorithm.")
```
```csharp
var key = slice<byte>((@string)("This is a test using a larger than block-size key " + "and a larger than block-size data. The key needs to " + "be hashed before being used by the HMAC algorithm."));
```

The gate is deliberately narrow — a `+` chain whose every leaf is a string literal that renders *plainly* — so it fires on exactly the shape that produces a bare C# `string` and nothing else. A non-constant concatenation (`s + "x"`) already yields an `@string` through its variable operand; an ident or selector naming a string constant emits its declared symbol; and a raw-byte (`\xHH`) leaf takes convBasicLit's byte-ARRAY route, which yields an `@string` that carries the whole chain with it (`slice<byte>("" + ((@string)(new byte[]{0xff, 0x80})))` — the `ByteTableStringVar` case, byte-identical across this change). (Guarded by `StringLiteralSliceConversion`, which now also covers the split `[]byte`/`[]rune` idiom and a raw-literal piece in the chain.)

### `string([]rune)` encodes an INVALID rune as U+FFFD, never fails

Go's rune-to-string conversions replace every invalid rune — a surrogate (`0xD800`–`0xDFFF`) or an
out-of-range value (`< 0` or `> 0x10FFFF`) — with `U+FFFD` (`utf8.RuneError`, bytes `EF BF BD`),
one replacement per invalid element: `string([]rune{0xD800})` is `"�"` (probed vs `go run`).
Every golib rune-span encoding routes through one seam, `builtin.ToUTF8Bytes` (the `@string`
rune-span constructor, the `slice<rune>`/single-`rune`→`@string` operators, and rune `append` all
land there), which used the element conversion `int` → `System.Text.Rune` — and that conversion
THROWS `ArgumentOutOfRangeException` for exactly Go's invalid values, killing the host instead of
producing the replacement bytes (strings' `TestCaseConsistency` builds a string of every rune
`0..MaxRune`, surrogates included — Phase-4 row R7). The encoder now uses
`Rune.TryCreate(value, out codePoint)` and substitutes `Rune.ReplacementChar` on failure; the
4-bytes-per-rune buffer estimate still covers the 3-byte replacement. (Guarded by the
`InvalidRuneString` behavioral test — slice and single-rune conversions over runtime values, byte
values and lengths compared vs Go; runtime values keep both compilers from constant-folding the
conversions.)

### Converting a string literal to a named string type
A Go conversion of a string **literal** to a named type whose underlying type is `string` — `errorString("…")` where `type errorString string` — needs the same `@string` intermediate. The literal renders as a `u8` `ReadOnlySpan<byte>`, which has no conversion to the named type, so a bare `(errorString)"…"u8` is CS0030. The converter routes it through `@string` (which converts implicitly from the `u8` span and to which the named type converts):

```go
return errorString("kaboom")
```
```csharp
return ((errorString)(@string)"kaboom"u8);
```

This is the form the runtime uses for every `panic(errorString("…"))` / `plainError("…")`. (Guarded by the behavioral test `NamedStringConversion`.)

**The same intermediate is needed with NO explicit Go conversion written — a named string type's
zero value in a RESULT position (2026-08-08).** Go converts an untyped string constant to a defined
string type implicitly, so the source says only `return nil, ""`; the emitted C# still has to cross
the two user-defined conversions, so a bare `""` has no conversion to the named type at all
(**CS0029**). In a multi-result return the damage spreads: the failed element leaves its tuple
siblings with no target type either, so the `default!` beside it is **CS8716**. The result type's
being a defined type over `string` is the signal, and the literal takes the `@string` step:

```go
// os/zero_copy_linux.go — poll.String is `type String string`
func getPollFDAndNetwork(i any) (*poll.FD, poll.String) {
    sc, ok := i.(syscall.Conn)
    if !ok {
        return nil, ""
    }
```
```csharp
return (default!, (@string)"");
```

A plain `string` result is deliberately excluded (it emits as `@string`, which a literal already
reaches in one conversion), as is a type parameter, whose emitted form is not a `[GoType]` wrapper.
An ALIAS of a named string type is the same type and takes the same route. (Guarded by the behavioral
test `NamedStringZeroValue` — the multi-result shape that carries the cascade, the single-result
shape, the alias, and the named type still behaving as a string under `+`, `+=` and `==`,
stdout-compared against `go run`.)

### A POSITIONAL struct-composite element in a `string` field renders `u8`
Go requires a positional composite literal to list every field in order, so element *i* is field *i*.
A string-literal element whose field is a plain `string` renders as the `u8` span, which binds the
generated constructor's `@string` parameter through one implicit conversion:

```go
type StructuralError struct{ Msg string }   // encoding/asn1
return StructuralError{"empty integer"}
```
```csharp
return new StructuralError("empty integer"u8);
```

KEYED elements (`fileListEntry{name: "./"}`) and ELIDED positional elements (`[]pair{{"e2", …}}`)
already emitted `u8` — they route through `convKeyValueExpr` and the elided element context
respectively — so this only closes the TYPED positional gap, where the element stayed a bare C#
string that `Encoding.UTF8.GetBytes` transcoded on every evaluation. It was the corpus's last
converter-emitted bare-UTF-16 literal class in a constructor position: 83 sites across 9 types
(asn1's `StructuralError`/`SyntaxError`, net/http's `ProtocolError`/`contextKey`, math/big's
`ErrNaN`, encoding/xml's `UnmarshalError`, net/url's `Error`, …). An `any` field keeps the boxed
`(@string)"…"u8` form (`markAnyFieldLits` runs after and overrides).

### A string-literal ELEMENT of a typed slice/array composite renders `u8`
The element twin of the struct-field rule above. An **elided** slice/array literal already rendered
`u8` (its element context is nil, and `convExprList`'s nil-context default leaves `u8StringOK` on),
but the **typed** form built its own `CallExprContext` and never set `u8StringArgOK`, so every
element fell back to a bare UTF-16 C# string that `Encoding.UTF8.GetBytes` re-transcoded on each
evaluation:

```go
return StructuralError{[]string{"Format specifies USTAR", whyNoUSTAR}}   // archive/tar
```
```csharp
return new headerError(new @string[]{"Format specifies USTAR"u8, whyNoUSTAR}.slice());
```

The span binds the `@string` element slot through `@string`'s implicit `ReadOnlySpan<byte>`
conversion; a NAMED type over `string` binds the same way through its generated span conversion, so
the rule keys on the element type's **underlying** basic kind (matching `markStringFieldLits`).
An **empty-interface** element type — `[]any{"a"}` — keeps its mandatory `(@string)` box but now
takes the `u8` half too (`new any[]{(@string)"a"u8}`): the cast is what makes the span boxable, the
`u8` is what keeps the bytes a compile-time constant. These two were the corpus's last
converter-emitted bare-UTF-16 literal classes — 385 `new @string[]{"…"}` element sites plus the
`any`-element form. KeyValueExpr elements (maps, sparse arrays) are not `BasicLit`s and route
through `convKeyValueExpr`, which already emitted `u8`. (Guarded by the behavioral tests
`AnyStringLitComposite`, `DeepEqual`, `GenericCompositeLiterals`, and 15 others whose goldens carry
the form.)

### A string CONCAT element of a composite literal keeps the `u8` span form
The two rules above mark a composite's string-typed element slots as span-tolerant, but they marked
only the elements that were `BasicLit`s. That flag does double duty: `convExprList` derives
`spanTargetUnsupported` from it, and `convBinaryExpr` reads *that* to suppress `u8` on a string
**concat**'s literal operand. A concat element is an `ast.BinaryExpr`, never a `BasicLit`, so it left
its own slot looking span-hostile and the literal fell back to bare UTF-16:

```go
allowed := []string{prefix + "-a", prefix + "-b"}
```
```csharp
var allowed = new @string[]{prefix + "-a", prefix + "-b"}.slice();     // before
var allowed = new @string[]{prefix + "-a"u8, prefix + "-b"u8}.slice(); // after
```

The suppression itself is correct where it was born — an `object[]` vararg slot cannot box a
`ReadOnlySpan<byte>`, so `fmt.Println(allowed[0] + ":" + msg)` must keep plain operands (CS1503) —
but a string element slot is not span-hostile, which the sibling spellings already proved: the same
concat rendered `u8` when parenthesized (at the time, because `convParenExpr` dropped the incoming
literal context — a defect in its own right, fixed in the next section) and when the composite's type
was elided (nil element context). Marking every positional element makes the three agree, and it is
what keeps the parenthesized spelling on the span form now that parentheses inherit their slot's
context instead of discarding it. The span operand then binds golib's `operator +(@string, ReadOnlySpan<byte>)`, which
block-copies the literal's ROM bytes straight into the single result buffer — no
`Encoding.UTF8.GetBytes` transcode, no throwaway intermediate `@string`. Two literal operands need no
operator at all: C# folds `"x"u8 + "y"u8` into one UTF-8 literal at compile time.

### A SLICED string literal in a concat needs one real `@string` operand

That last sentence is the whole hazard: C#'s `+` over UTF-8 spans is a **literal-only** compile-time
feature, and a *slice* of a literal is not a literal. Go's `format_test.go` builds a fraction the
obvious way:

```go
nanosec, err := strconv.ParseUint("012345678"[:test.fracDigits]+"000000000"[:9-test.fracDigits], 10, 0)
```

Both operands render as `"…"u8[..n]`, i.e. two bare `ReadOnlySpan<byte>` values with no operator
between them (CS9047). The literals deliberately keep their `u8` form — Go slices a string by **bytes**,
so re-rendering them as C# `string` and slicing that would index by UTF-16 code units, right for ASCII
and silently wrong for anything else. Instead the sliced operand is cast to `@string`, which gives the
concat one real operand and lets golib's `operator +(@string, ReadOnlySpan<byte>)` (or its mirror) bind
while the other half stays a span:

```csharp
strconv.ParseUint(((@string)"012345678"u8[..(int)(test.fracDigits)]) + "000000000"u8[..(int)(9 - test.fracDigits)], 10, 0)
```

Only `token.ADD` is affected; a **comparison** against a sliced literal already binds golib's span-aware
operators and keeps its zero-allocation form. (Guarded by the `PackageNameShadowing` behavioral test,
case 5.)

For a slot of a **named** string type the old form did not merely cost a transcode — it did not
compile. The generated `[GoType("@string")]` wrapper carried the span *comparison* operators but no
`+` at all, so C# fell back to converting both operands to a C# `string` and calling `string.Concat`:
the result was a `string` where the wrapper was wanted (CS0029 in an element slot, CS1503 in a
generated constructor), and a `u8` operand had no candidate to reach at all (CS0019).
`InheritedTypeTemplate` now mirrors `@string`'s concat set — `+(T, T)`,
`+(T, ReadOnlySpan<byte>)`, `+(ReadOnlySpan<byte>, T)` — so a named string type survives a concat
exactly as Go says it does, keeping its method set:

```go
type version string
func (v version) tag() string { return string(v) + "!" }

short := base + "-rc"   // still a version, so short.tag() stays callable
```
```csharp
version @short = @base + "-rc"u8;
```

The same `BasicLit`-only gate applied to `markStringFieldLits`, so a positional STRUCT element in a
string field had the identical defect (`rec{base + "-s"}` over a `version` field was CS1503); both
gates now mark every positional element. KEYED elements still skip — index *i* is not field *i* for
them, and `convKeyValueExpr` resolves their slot itself. (Guarded by the behavioral tests
`CompositeElementStringConcat`, which also pins the vararg suppression that must NOT change, and
`NamedStringConcat`; `ReturnTupleFuncLitArg`'s golden carries the slice-element form.)

### Parentheses inherit their slot's literal context
Parentheses are transparent in Go: `(x)` lands in the enclosing slot exactly as `x` does. But
`convParenExpr` rendered its operand with a **fresh** context list — it built the `StarExprContext`
the pointer-cast path needs and passed only that — so the incoming `BasicLitContext` never reached the
operand. The slot's span-tolerance signal (`spanTargetUnsupported`, above) was silently reset to the
default *tolerant*, and a pair of parentheses could re-enable the `u8` span form inside a span-**hostile**
slot:

```go
panic("a" + "b")     // suppressed correctly
panic(("a" + "b"))   // parenthesized — the same slot, the opposite rendering
```
```csharp
throw panic("a" + "b");         // before and after
throw panic(("a"u8 + "b"u8));   // before — CS1503
throw panic(("a" + "b"));       // after
```

C# folds two adjacent utf8 literal constants into a single `ReadOnlySpan<byte>`, and a span has no
boxing conversion to `object`, so the parenthesized spelling did not merely differ — it did not
compile. `panic` is where this surfaces because it is the one span-hostile slot with no second line of
defence: the others pick up an outer `(@string)` box cast whose helper (`constExprIsStringLiteralConcat`)
already unwraps `ParenExpr`, while `panic` short-circuits in `convCallExpr` before `convExprList` ever
runs. With a **non**-constant operand the drop was invisible rather than fatal — `(a + "-y"u8)` binds
golib's `operator +(@string, ReadOnlySpan<byte>)` and yields an `@string`, which boxes fine — but it
still rendered the parenthesized and unparenthesized spellings of one expression differently.

The fix threads the incoming literal context *through* the paren arm instead of replacing it; the
`StarExprContext` is passed alongside it, so the pointer-cast path is unchanged. Every span-hostile
slot — `panic`, a vararg `any`, an interface-typed assignment, return, or `ValueTuple` element — now
renders both spellings alike.

The **inverse** must hold too: a composite literal's string element slot *is* span-tolerant, and its
parenthesized concat has to keep the span form. It does, through that composite's own per-element gate
(previous section) rather than through the dropped context — which is exactly why the two changes
belong together:

```go
elems := []string{a + "-g", (a + "-h")}
```
```csharp
var elems = new @string[]{a + "-g"u8, (a + "-h"u8)}.slice();
```

Re-transpiling the behavioral corpus and reconverting the full standard library both produce
byte-identical output, so this is latent-defect hardening rather than a rendering change: no site in
either corpus spells a concat this way today. (Guarded by the behavioral test
`ParenthesizedConcatContext`, which spells every affected slot **both** ways so the pair must agree.)

One related gap is deliberately left open. The decisions that mark a string literal boxable —
`u8StringArgOK` / `useGoStringArg` in `convCallExpr`, and the same test in `visitSendStmt`,
`convKeyValueExpr`, `convCompositeLit`, `markAnyFieldLits`, and `convFuncLit` — all gate on
`isStringBasicLit`, a bare `*ast.BasicLit` type assertion that does **not** unwrap parentheses (unlike
`constExprIsStringLiteralConcat`, which does). So a parenthesized *standalone* literal in an `any` slot
(`fmt.Println(("lit"))`) is not
recognized as one, and now renders `(@string)(("lit"))` rather than the constant-span
`(@string)(("lit"u8))` it got by accident from the dropped context — correct, and one
`Encoding.UTF8.GetBytes` per evaluation slower. Making those gates paren-aware is the general fix for
that family; it is a distinct change with its own footprint and is not folded in here.

### Converting a string literal to a named `[]byte` / `[]rune` type
The byte/rune-slice sibling of the named-string rule above: a string **literal** converting to a named type whose underlying is `[]byte` or `[]rune` — `htmlSig("<!DOCTYPE HTML")` where `type htmlSig []byte` (net/http `sniff.go`'s signature table) — cannot cast directly either. The `u8` span converts to neither the `[GoType]` wrapper (whose implicit operator takes exactly its underlying `slice<byte>`/`slice<rune>`) nor through `@string` in one hop (C# chains at most one user-defined conversion — CS0030). The converter materializes the underlying slice exactly the way the plain `[]byte("…")` conversion does (the `slice<T>(T[])` builtin over the literal's `@string`), and the wrapper's own operator then applies:

```go
type htmlSig []byte
sig := htmlSig("<!DOCTYPE HTML")
```
```csharp
var sig = ((htmlSig)slice<byte>((@string)"<!DOCTYPE HTML"u8));
```

The rune form decodes code points — `runeSig("héllo")` yields a rune-counted `slice<rune>` — matching Go's conversion semantics. String **variables** are unaffected (no instance in the corpus; a named-slice wrapper conversion from a `@string` variable would surface as a loud CS0030, not silent misbehavior). (Guarded by the behavioral test `NamedByteSliceFromStringLit` — direct, composite-element, and argument positions, byte/rune element reads, all output-compared vs Go.)

### A string literal with high raw-byte escapes (`\xHH` hex, `\NNN` octal) emits a byte-array `@string`
Go's `\x` escape is **exactly two** hex digits denoting one raw byte; C#'s `\x` escape is a **greedy** 1-to-4-hex-digit code-*unit* escape, and a C# `"…"u8` literal UTF-8-re-encodes its content. So re-emitting a Go token verbatim as a C# string literal both (a) mis-parses `\xdb` followed by ASCII `"5""0"` (the token `\xdb50`) as the single code unit U+DB50 — a lone high surrogate that cannot UTF-8-encode into a golib `@string` (CS9026, time/tzdata's embedded zip blob) — and (b) silently widens every byte ≥ 0x80 to two UTF-8 bytes, so `@string` byte indexing / `len` would not match Go. Such literals are emitted as the exact bytes in a **parenthesized** byte-array-backed `@string`:

```go
const zipdata = "\x50\x4b\x03\x04\xdb50\xff\x92\x00LMT"   // raw bytes
```
```csharp
internal static readonly @string zipdata = ((@string)(new byte[]{0x50, 0x4b, 0x03, 0x04, 0xdb, 0x35, 0x30, 0xff, 0x92, 0x00, 0x4c, 0x4d, 0x54}));
```

The outer parentheses are load-bearing: an inline-indexed literal (`"…"[i]`) would otherwise bind `[i]` to the inner `byte[]`. Only a raw-byte **escape** trips it — for `\xHH`, a byte value ≥ 0x80 or a trailing hex digit (the octal companion is below) — so a literal written with actual UTF-8 characters (`"Michał"`, `"白鵬翔"`) round-trips through `"…"u8` and keeps the readable string form, as does an all-ASCII escape run with no greedy extension (image/jpeg's `"\x00\x10\x01\x11"u8[i]`), and there is no behavioral-golden churn. (Guarded by the `HexByteStringLiteral` behavioral test.)

Go spells a raw byte **two** ways, and the same rule covers both: `\NNN` is **exactly three** octal digits, likewise denoting one byte. Octal has no *greedy* hazard — Go's escape is exactly three digits and the C# `\uXXXX` it would render as is exactly four hex digits, so neither side can extend into the following text — but the **byte-width** hazard is identical and just as silent: Go's `"\377"` is the single byte `0xFF`, whereas the character `U+00FF` that `replaceOctalChars` would emit UTF-8-encodes to the **two** bytes `0xC3 0xBF`. The UTF-16-string and `u8` renderings produce those same wrong bytes, so an octal escape ≥ `\200` takes the byte-array path as well; below `\200` it is ASCII-safe and keeps the readable form:

```go
const octalData  = "\377\200\303\277\101\000\177Z"   // 8 raw bytes
const asciiOctal = "\101\102\011\103"                // ASCII "AB\tC"
```
```csharp
internal static readonly @string octalData = ((@string)(new byte[]{0xff, 0x80, 0xc3, 0xbf, 0x41, 0x00, 0x7f, 0x5a}));
internal static readonly @string asciiOctal = "\u0041\u0042\u0009\u0043"u8;
```

Left unfixed, `len(octalData)` is 12 rather than 8 and every byte index past the first is wrong. This one is worth recording as a *latent* defect: it was found by design review, not by a miscompile, and the corpus had **no instance** of it (CNR is byte-identical across the behavioral corpus, and the rule is purely additive — it can only divert literals that were already being emitted with the wrong bytes). Note that the folded-value rule below catches the octal case for *concatenated* constants by a different test (`utf8.ValidString`), since a lone `\377` byte is not valid UTF-8. (Guarded by the extended `HexByteStringLiteral` behavioral test — a high-octal table, the `\200` low boundary, sub-0x80 controls asserting the readable form survives, and a non-const local, all byte-indexed and `len`-measured, output-compared vs `go run`; `stringLiteralNeedsByteArray`'s rule — both escape forms, the sub-0x80 controls, and the escaped-backslash parity cases — is unit-tested in `convBasicLit_test.go`.)

The **sub-`\200` rewrite** that renders the readable form obeys the same backslash-parity rule, and
it did not. `replaceOctalChars` matched `\NNN` with a plain regex, so in `"\\101"` — an *escaped
backslash* followed by the ordinary characters `1`, `0`, `1` — it matched from the SECOND backslash
and emitted `"\\u0041"`, whose C# value is the six characters `\u0041` where Go's is the four
characters `\101`. Wrong content, wrong length, silently. (`"\\377"` is the same case above the
diversion boundary: parity keeps it out of the byte-array path too, since it holds no raw byte.) The
rewrite is now a positional parity scan — a `\NNN` is an escape only after an ODD run of
backslashes — which also fixes a second defect of the regex form: it paired `FindAllString` with
`strings.Replace(…, 1)`, replacing the first *textual* occurrence of each match rather than the
matched position, so a literal carrying both forms rewrote the escaped one twice:

```go
const escapedOctal = "\\101|\101|\\\101|\\377"   // Go: `\101` | 'A' | `\`+'A' | `\377`
```
```csharp
internal static readonly @string escapedOctal = "\\101|\u0041|\\\u0041|\\377"u8;
```

Three octal digits cap at `\777` = 0x1FF, so the C# `\uXXXX` code-unit escape always suffices (the
old `\UXXXXXXXX` branch was unreachable). The same helper feeds the `token.CHAR` path, where the
parity case cannot arise (a rune literal holds one character) but the escape rewrite is shared. This
was found by review, not by a miscompile: CNR shows no corpus instance (byte-identical apart from
the guard's own golden). (Guarded by the extended `HexByteStringLiteral` behavioral test — both
forms as a const and as a local, plus the rune pair, output-compared vs `go run` — and by
`TestReplaceOctalChars`.)

The above routes a single `*ast.BasicLit` through `convBasicLit`'s scan. A string **constant** whose value is a *concatenation* — `const rev8tab = "" + "\x00\x80…" + …` (math/bits' bit-reversal table) — folds to one value with **no** single `BasicLit`, so it bypassed that scan and rendered a UTF-16 string literal: `rev8tab[1]` returned `0xC2` (the UTF-8 lead byte of U+0080), not `0x80`, and `Reverse8` was wrong. The const-string path now tests the FOLDED value directly — a value that is not valid UTF-8 (`utf8.ValidString`) cannot round-trip through a C# string/u8 literal, so it emits the same byte-array `@string` from its exact bytes (`byteArrayStringLiteral`, shared with `emitByteArrayString`); a valid-UTF-8 value keeps the readable `getStringLiteral` form. This catches any non-UTF-8 byte table built by concatenation (crypto S-boxes, embedded blobs), not just single literals. (Guarded by the `ByteTableStringConst` behavioral test — a concatenated `\x00\x80…` table byte-indexed and `len`-measured, output-compared vs `go run`; the pre-fix converter returns `0xC2` for index 1. The full corpus compiles with the byte-array consts, and CNR is byte-identical.)

**Valid UTF-8 is not sufficient, and that gap cost `net/http/fcgi` a row (2026-08-09).** The folded
arm's `utf8.ValidString` test answers only the byte-widening half of the round-trip; the *greedy-
escape* half above applies to a folded constant exactly as it does to a `BasicLit`. FastCGI's
`const want = "\x01\n\x00\x00\x00\x12\x06\x00" + "\x0f\x01FCGI_MPXS_CONNS1" + …` folds to a value
that is entirely ASCII — perfectly valid UTF-8 — so it took the readable path and emitted
`\x0f\x01FCGI…`, in which C# reads `\x01F` as U+001F and eats the `F`. Nothing failed to compile;
`TestGetValues` simply compared a correct response against its own corrupted constant, and reported a
`%q` diff whose cause is invisible unless you already know C#'s escape is variable-length. The arm
now applies `stringLiteralNeedsByteArray` to the folded value's own quoted form, so BOTH declaration
routes ask exactly the same question and diverge only where the answer genuinely differs. Corpus
reach, measured before the fix: one live site — every other `\x`+hex-digit run in the emitted corpus
sits inside a C# *verbatim* (`@"…"`) literal, where `\x` is two ordinary characters. (Guarded by the
extended `ByteTableStringConst` behavioral test, whose second constant is the ASCII-only
`"\x0f\x01" + "FCGI_MPXS_CONNS1" + "\x0a\x0d" + "BEEF"` — two greedy sites, byte-indexed, `len`- and
`%q`-printed, output-compared vs `go run`; and by `net/http/fcgi`'s banked suite.)

The **`var`** form of the same table needs no separate rule, and it is worth stating why, because
the two declaration kinds reach the byte-array emission by genuinely different routes. A `const`
is *folded* by go/types, so the concatenation is gone by the time the declaration is emitted and
only the folded value can be inspected — hence the `utf8.ValidString` test above. A `var`'s
initializer is *rendered as an expression*: `var tbl = "" + "\xff…" + …` walks the `BinaryExpr` and
converts each operand through `convBasicLit`, so every piece is scanned on its own and the
non-UTF-8 pieces become byte-array `@string`s that then concatenate as `@string`s:

```csharp
internal static @string tbl = ""u8 + ((@string)(new byte[]{0xff, 0x00, 0x80})) + ((@string)(new byte[]{0x01, 0xfe}));
```

This holds for a package-level var, a function-local var, an explicitly typed var
(`var t string = …`), a single non-concatenated literal, and a `[]byte("" + "\xff…")` conversion.
Note `encoding/hex`'s `reverseHexTable` — the 256-byte table that motivated a second look at this
area — is a **`const`**, already covered by the folded-value rule; the corrupted UTF-16 literal
still visible in a stale `src/core/encoding/hex/hex.cs` is pre-fix output, not current
converter behavior. (Guarded by the `ByteTableStringVar` behavioral test — package-level, local,
typed, and single-literal non-UTF-8 tables byte-indexed and `len`-measured, plus valid-UTF-8
controls asserting the readable literal form and UTF-8 byte-count `len`, output-compared vs
`go run`.)

### A value-materializing string literal is HOISTED to a `static readonly` field beside its first use
Go keeps string literals in RODATA: `return "true"` allocates **nothing** in a Go binary. Emitted
inline, the converted C# pays a fresh backing `byte[]` at *every evaluation*, because each
literal→`@string` materialization copies the `u8` span. A whole-package pre-pass therefore hoists
each package-unique literal that materializes a VALUE to one `private static readonly` field,
declared immediately above the function whose body holds its first package-wide use, and every use
site becomes a field reference — so the literal costs at most one allocation per program *run*:

```go
// strconv/atob.go
func FormatBool(b bool) string {
	if b {
		return "true"
	}
	return "false"
}
```
```csharp
// Hoisted @string literals (single allocation; Go keeps these in RODATA)
private static readonly @string trueˢ = "true"u8;
private static readonly @string falseˢ = "false"u8;

// FormatBool returns "true" or "false" according to the value of b.
public static @string FormatBool(bool b) {
    if (b) {
        return trueˢ;
    }
    return falseˢ;
}
```

**What hoists.** Only contexts that materialize a value, where a shared immutable `@string` is
indistinguishable from a fresh one: a value `return`; an assignment to a local, parameter or struct
field; an argument bound to a `string` parameter (including a variadic `...string` element); an
`any`/empty-interface target (argument, result, channel send, assignment); a standalone map-index
key; and a conversion of the literal to a named string type. A literal whose EVERY package use is an
`any` target is emitted **pre-boxed** — `private static readonly object xˢ = (@string)"…"u8;` — so
those sites allocate nothing at all. Mixed-use literals get one `@string` field and box per `any`
call; there are never two fields for one literal.

**What does not hoist**, and why — deterministic filters, not hotness heuristics:

| Context | Why it stays inline |
|:--|:--|
| comparison operands (incl. lowered `switch` chains) | already zero-allocation: `@string` and every named string type compare against a `u8` span in place |
| concat operands (`x + "…"`) | `operator +(@string, ReadOnlySpan<byte>)` already consumes the span without materializing it |
| `[]byte("…")` / `[]rune("…")` sources | the result must be freshly MUTABLE; that one allocation is mandatory |
| `fmt`/`log`/`testing` `*f` **format-position** literals (recognised structurally: a variadic callee named `…f` with a `string` parameter immediately before the variadic) | a format string slugs badly (`"%v"` → `vˢ`) and a formatting call's cost is dominated by formatting itself |
| **degenerate slugs** — no usable ASCII word content, or a slug of ≤ 3 characters | `strˢ7` / `dˢ` carry no information; the literal reads better inline. `"true"` slugs to a healthy four-character `true` and DOES hoist |
| the empty literal `""` | already 0 B (`ToArray()` of an empty span returns `Array.Empty`) |
| composite-literal elements and keys | uniform hoisting would emit thousands of fields above the table-building functions and move their allocations out from under a `sync.Once` into the type initializer. (A *standalone* index into the same map — `table["composite key"]` — still hoists) |
| literals inside `func init()` | run exactly once by construction |
| package-level `var`/`const` initializers — decided on the **Go AST position**, not the emitted C# shape | one-time by nature (a package-level table is emitted into an `initᴛ*` method body, which a shape-based rule would mistake for an ordinary function) |
| func literals OUTSIDE a function declaration | no `FunctionPrefixMarker` anchor exists to hoist above |
| `\xHH` / high-octal raw-byte literals | already diverted to the byte-array-backed `@string` path |
| every declaration a `[module: GoManualConversion]` file or entry owns | its emission is redirected to a non-compiled `.cs.auto` (or replaced by a placeholder comment), so it renders no prefix marker and must never CLAIM a field; its own literals stay inline, and the reconvert gate asserts no hoisted field is ever declared in a `.cs.auto` |
| universe builtins (`panic`, `print`, `copy`, `unsafe.Slice`, …) | `go/types` records a call-site-specific signature for these, but the converter emits each through its own path — `panic` deliberately keeps the bare interned literal, which is zero-cost until a panic actually fires |

**Naming.** `HoistedLiteralMarker` (`ˢ`, U+02E2 — a new `symbols.json` entry, never hardcoded)
suffixes a camelCase slug of the literal's own content, joined at word boundaries and truncated at
≤ 24 characters. The alphabet is **ASCII** letters and digits, not `unicode.IsLetter`: a C#
identifier is lexed over UTF-16 code units, so a letter outside the BMP is a surrogate pair and can
never appear in one — `go/types` spells its universe type set `"𝓤"` (U+1D4E4, category Lu), and a
rune-wide slug emitted `𝓤ˢ`, a CS1056/CS1519 cascade. An ALL-CAPS word folds whole
(`"TESTING KEY"` → `testingKeyˢ`, `"CONTENT-TYPE"` → `contentTypeˢ`); touching only its first
character would leave `tESTINGKEY`, which was 9% of the corpus' hoisted names on the first cut.
Distinct literals whose slug collides take a package-wide first-occurrence ordinal (`fooˢ`, `fooˢ2`),
checked against the package's declared names *and* the already-claimed hoist names —
`performNameCollisionAnalysis` walks Go declarations only and never sees a synthetic name. Because
every hoisted name ends in `ˢ`, it can collide with neither a C# keyword nor a Go-derived identifier.

**Two orderings the mechanism has to respect.**

*Initialization order.* C# runs static field initializers in textual order within a class PART and in
**unspecified** order across parts, so a package-level `var` whose initializer transitively reads a
hoisted field could observe `default(@string)` (`""`). The converter already owns the defense —
`initOrderOperations` relocates dependency-ordered initializers into the generated static
constructor, which runs after ALL field initializers — but its graph is keyed on Go variables and
cannot see a synthetic field. Every function that reads a hoisted field is therefore registered in
that graph, so any package-level initializer reaching one transitively is relocated. Three live
corpus instances surfaced immediately: `net/http/internal/testcert`'s `LocalhostKey =
testingKey(…)` reads two hoisted fields declared *later in the same file* and would have run
`strings.ReplaceAll(s, "", "")`; `internal/profile` and `runtime/pprof` are the other two.

Where the relocation is **unavailable** the rule takes a second arm: the `-tests` variant conversion
does not run `collectMovedInitVars` at all (the test project has no `package_init.cs` emission path,
and an internal variant shares the production class, which may already own a static constructor — a
second one is CS0111), so there nothing inside a function a package-level initializer can REACH is
hoisted at all; those sites keep the inline rendering, and the same literal still hoists from any
other use. This is not hypothetical: `encoding/pem`'s `var pemData = testingKey(…)` is declared
~300 lines above the `testingKey` whose two hoisted fields it depends on, ran
`strings.ReplaceAll(s, "", "")`, and left every `"TESTING KEY"` in place. The corpus **compiled
clean** with that bug present — only running the package's own Go tests found it.

*Two-pass `-tests` conversion.* An internal `_test.go` file emits into the PRODUCTION package class
and can sort BEFORE the production file that owns a field. The test pass's registry is therefore
pre-seeded with the production literal→field map (recomputed by the same collector over the
production files, with their manual-conversion flags), and a test file may only REFERENCE a seeded
literal, never claim it — that is what prevents CS0102, not name luck. An EXTERNAL `<pkg>_test`
variant carries no production files, so its seed is empty and it claims freely into its own class,
which is required: a production field is `private` to a different class. Production output is
byte-identical whether or not tests are converted.

**Determinism.** File conversion is sequential in sorted-filename order (concurrency was removed for
exactly this reason), and names and placement derive only from literal content plus source order, so
two runs over the same tree emit the same bytes. Emission itself is a pure substitution at
`convExpr`'s single `*ast.BasicLit` arm; the decision cannot be made there, because pre-boxing needs
every use of a literal and the init-order rule needs the reader set before any file emits.

One interaction is worth naming: `applyUntypedConstBoxCast` re-applies the `(@string)` default-type
box cast to anything that does not already lead with it, and a hoisted name does not. It now skips a
hoisted literal outright — an `@string` field needs no cast, and re-casting a PRE-BOXED `object`
field would unbox and allocate a fresh box on every evaluation, defeating the hoist at exactly the
`any`-slot sites it targets.

Corpus effect (Go 1.23.1, 302 packages): **3,253 hoisted fields** across 467 files, 62 of them
pre-boxed. Per *function* the blocks are small — 1,634 blocks, median 1, p90 4, p99 11, max **61**
(`net/http`'s `StatusText`, the worst case the design accepted up front). Per file the median is 3
and the max 127, in the 20k-line bundled `net/http/h2_bundle.cs`. A side effect worth recording: two
dead deref-alias prologues disappeared (`runtime`'s `lfnodeValidate`, `go/types`' `suspendedCall`)
because `bodyReferencesIdentAsValue` is a text test whose own comment names "a string" as a source of
spurious matches — the words *node* and *call* inside those functions' message literals were the only
textual occurrences keeping the aliases alive. Moving the literal out of the body drops the dead
local; a genuinely live alias is still never dropped, since a real value use emits the identifier
regardless. (Guarded by the `StringLiteralHoisting` behavioral test — every row of both tables above,
plus slug-collision ordinals, cross-file dedupe, and the init-order case, output-compared vs
`go run`.)

### Composite types render structurally (`[]*T` keeps the pointer)
A slice/array type is rendered structurally in every type-name path: the `[N]`/`[]` marker plus the recursively resolved element, never from the `go/types` string form. The string form is path-qualified (`[]*internal/abi.Type`), and the cross-package last-segment strip would eat everything before the slash *including the pointer marker*, silently dropping the `ж<>` (reflect's `[]*abi.Type` fields compiled against the WRONG element type). The recursion also resolves lifted anonymous elements and cross-package generic elements:
```go
ptrs := vals.([]*atomic.Int32)
```
```csharp
var ptrs = vals._<slice<ж<atomic.Int32>>>();
```
Guarded by `ArrayOfCrossPackageType` (the type assert and a `var` declaration).

A **SAME-PACKAGE instantiated generic** is rendered structurally for the same reason — the name plus each type argument recursively resolved, never from the `go/types` string. A *cross-package* generic already took the structural path (`getAliasQualifiedTypeName`/`getFullyQualifiedTypeName` both special-case `pkg != v.pkg`), but a generic whose OWN type is local while a type ARGUMENT is cross-package fell through to the `t.String()` form: `curve[*repro/sub.Item]`, whose slash-strip then ate everything before the `/` — **including the `curve[` header** — collapsing the wrapper. crypto/elliptic's `var p224 = &nistCurve[*nistec.P224Point]{…}` and its `p256Curve struct { nistCurve[*nistec.P256Point] }` embed emitted `ж<nistec.P224Point>>` / `ref go.nistec.P256Point> …` (a CS1519/CS1526 cascade, ~137 errors across elliptic/ecdh/mlkem768). Both `getAliasQualifiedTypeName` (the var-type path) and `getFullyQualifiedTypeName` (the struct-embed field path) now render a same-package generic as `Name[args…]` with each arg via the same function, so the arguments carry their short, slash-free package-qualified names and the header survives → `ж<nistCurve<ж<nistec.P224Point>>>`. Byte-identical across the behavioral corpus; an A/B of crypto/elliptic+ecdh shows only wrapper-restorations at every site (var types, adapter ctors, `GoImplement` attributes, the embed accessor, the unmarshaler array). (Guarded by the `CrossPkgUser` extension — a same-package `Holder[*CrossPkgLib.Sensor]` as a var type AND a struct embed, field read/write vs Go.)

### A reference-type-pointee pointer parameter uses the nil-check-free `.ValueSlot` deref alias

The entry deref-alias for a pointer parameter is `ref var p = ref Ꮡp.Value`. The `.Value` getter
throws `NilPointerDereference` when the box reports `IsNull` — for a MANAGED box, `m_val is null`.
That is correct when the pointee is a VALUE type (a null `m_val` means a genuinely nil pointer). But
when the POINTEE is itself a reference type — `*error`, `*[]T`, `*map[K]V`, `**T`, `*func(…)`,
`*chan T` — the box holds the reference VALUE directly, and that value is legitimately null when it
is the zero value (a nil interface/slice/map). The pointer is still a valid, non-nil box (`Ꮡ(err)`),
so establishing the entry ALIAS is a read of the held value, not a dereference of the box: in Go,
`*(&err)` of a nil `error` yields nil, no panic. `.Value`'s `IsNull` check misfires on `m_val is
null` and panics spuriously at function entry — text/scanner's `digits(…, invalid *bool)` and, for a
reference pointee, text/tabwriter's `handlePanic(err *error)` (deferred from `Write`, whose `err` is
a nil named-return interface) crashed with a nil-pointer panic before `recover()` even ran. The fix:
when the pointee `isInherentlyHeapAllocatedType`, emit the nil-check-free `.ValueSlot` accessor
(`ref var err = ref Ꮡerr.ValueSlot`), mirroring `namedResultBoxAccessor` — a named result of the same
type already reads this way. `.ValueSlot` returns the same real `m_val` slot as `.Value` in every
non-throwing case, so write-through and non-null reads are byte-behaviorally identical; only the
spurious-panic case changes. Corpus-wide the swap touches 49 stdlib files + 10 behavioral goldens, all
value-preserving (full behavioral suite Output 0-fail). (Guarded by the `PointerToInterfaceParamDeref`
behavioral test
— a `*error` parameter read through inside a deferred recover/re-panic where the pointee is nil at
address-of time; before the fix the entry alias NREs, after it prints the re-panic message,
output-compared vs `go run`.) ⚠ This fixes only the spurious CRASH. A SEPARATE latent defect remains:
a non-heap-promoted address-taken named return — `Ꮡ(err)` boxes a COPY — so `*err = …` in the
deferred handler writes the copy while `return err` reads the original; text/tabwriter's tests need
that heap-promotion of address-taken named returns before they fully validate.

The value-type nilable case — a genuinely nil `*rune`/`*bool`/`*int` optional-out-param, deref'd only
under a body VALUE guard — was handled for a fortnight by a companion **call-site nil-argument**
detection, and is now subsumed by the unconditional nil-deferring entry alias (see *A pointer
PARAMETER is nil-deferring for exactly the reason a receiver is*). The problem it solved is worth
keeping on the record, because it is the cleanest demonstration of why an entry-alias policy cannot be
an analysis: `collectNilSafePtrParams` scanned only the body for `param == nil`/`!= nil`, so
text/scanner's `digits(ch0 rune, base int, invalid *rune)` — whose sole deref `*invalid == 0` sits
behind `ch >= max` (never `invalid != nil`) and which is called `digits(ch, 10, nil)` — kept the
strict `.Value` entry hoist and NRE'd at entry, where Go never dereferences (`ch >= max` is false on
the nil-call path). The remedy was a package-wide pre-pass (`collectNilArgPtrParams`) recording, per
`*types.Func`, the pointer-parameter positions ever passed the untyped `nil` at a call site — which
worked, but only for SAME-package call sites, because the converter processes one package at a time.
A parameter passed nil solely from another package stayed strict and stayed broken; that residual is
what `.DerefOrNull()` closes structurally, and the pre-pass was deleted with the rest of the analysis.
(Still guarded by the `GuardedNilPointerParamDeref` behavioral test — a `*int` out-param deref'd under
an `i >= base` guard, called once with a real pointer and once with nil; NREs at the entry hoist under
either predecessor, matches `go run` now without one.)

### A pointer-element composite literal takes the box for a deref-aliased ident

A bare identifier element of a pointer-element composite literal (`[]*CommentGroup{c}`) renders
the pointer VALUE — the box `Ꮡc` — not the deref'd receiver ref-local `c`. Every named pointer
parameter is deref-aliased in C# (`ref var c = ref Ꮡc.Value`), and the bare name is the value
alias; the array element type is `ж<CommentGroup>`, so the alias form was CS0029 (go/ast's
`CommentMap.addComment` — the sibling `append(list, Ꮡc)` already took the box through the
call-argument pointer arm). The routing mirrors the struct-field pointer arm: the element index
is marked `argTypeIsPtr`, which convExprList turns into the pointer ident context:
```csharp
list = new ж<CommentGroup>[]{Ꮡc}.slice();
```
Gated to bare idents of pointer type — keyed elements (maps) and address-of/composite elements
manage their own pointer rendering. Guarded by the `PointerParamWalk` extension `collect` (the
literal arm and the append arm, aliasing proven by a post-collect write through the original).

### A pointer value passed to an `any` argument takes the box
A deref-aliased pointer passed WHOLE (as an argument, not `p.field`) to an EMPTY-interface (`any`)
parameter renders the pointer VALUE — the box `Ꮡp` — not the deref'd value alias `p`. Go boxes the
*pointer* into the interface, so dropping the box stores the pointed-to VALUE and loses pointer
identity: a later `x.(*T)` assertion (rendered `._<ж<T>>()`) then finds a bare `T` and panics
("interface conversion: … is T, not *T"). This is fmt's own `sync.Pool` round-trip —
`func (p *pp) free() { … ppFree.Put(p) }` (Put's parameter is `any`) feeding `newPrinter`'s
`ppFree.Get().(*pp)` — which crashed the SECOND time through the pool, blocking every multi-call fmt
program. Both a pointer RECEIVER and a plain `*T` PARAMETER take the box:
```go
func (p *pp) free()  { poolPut(p) }   // p is *pp (pointer receiver); poolPut(x any)
func keep(q *pp)     { poolPut(q) }   // a plain *T parameter, same shape
```
```csharp
internal static void free(this ж<pp> Ꮡp) {
    ref var p = ref Ꮡp.Value;
    …
    poolPut(Ꮡp.OrTypedNil());          // NOT poolPut(p) — a pp VALUE loses pointer identity
}
internal static void keep(ж<pp> Ꮡq) {
    poolPut(Ꮡq.OrTypedNil());
}
```
(The `OrTypedNil()` suffix is the other half of the same boundary — see
[A pointer crossing into an interface carries its static type](#a-pointer-crossing-into-an-interface-carries-its-static-type-however-the-pointer-was-produced),
which generalized this arm from the call-argument slot to every empty-interface slot.)
This mirrors the composite-literal element arm above: the argument index is marked `argTypeIsPtr`,
which convExprList turns into the pointer ident context, so `convIdent` emits the parameter box
(`Ꮡp`) or the current method's direct-ж receiver box. It fires ONLY for the empty interface — a
NON-empty interface already routes the pointer through its `*T`→interface adapter (`interfaceTypes`),
and the two arms are mutually exclusive. A pointer LOCAL is excluded (it already holds its box
directly — the bare name IS the box), an `unsafe.Pointer` argument is excluded (not a `*types.Pointer`),
and the treatment fans out across a variadic `...any`. The receiver form reaches through a closure
too — `Ꮡs.Value.d.note(Ꮡs)` for `s.d.note(s)` inside a nested lambda (the database/sql `(*Stmt)`
shape). Guarded by `PointerValueToInterfaceArg` (a minimal sync.Pool-shaped free list round-tripping
a `*pp` via both a pointer receiver and a pointer param, each `.(*pp)`-asserted after the `any` hop —
the 2nd pool Get panicked before the fix) and the `NestedLambdaReceiverField` receiver-in-closure case.

### Appending to an interface-typed slice casts the element
A value appended to a `[]Iface` slice whose type is not already the interface -- a pointer rendering as the `*T`-to-interface adapter ctor, or a raw struct value -- leaves both golib `append` overloads applicable (`append<T>(ISlice, params T[])` infers the concrete/adapter type; `append<T>(slice<T>, params Span<T>)` infers the interface -- CS0121). The converter casts such elements to the element interface type:
```csharp
pack = append(pack, (Animal)(new CatжAnimal(Ꮡ(new Cat(nil)))));
pack = append(pack, (Animal)(new Dog(nil)));
```
An already-interface-typed element stays bare. The **empty interface** (`any`) element type is affected identically and takes the same cast — `append(args[:len(args):len(args)], c.output)` with `args []any` and `c.output []byte` infers `T=[]byte` on the `ISlice` overload but `T=any` on the `slice<T>` overload (testing's `flushToParent`, CS0121), and appending a scalar (`append(anys, 5)`) is the same shape; the differing element is cast to `any` so both overloads agree:
```csharp
args = append(args.slice(-1, len(args), len(args)), (any)(c.output));
```
Guarded by `InterfaceCasting` (non-empty interface) and `AppendUntypedConst` (the empty-interface `[]byte`-into-`[]any` and scalar-into-`[]any` cases).

### A `nil` element appended to a slice casts to the element type
A bare `nil` appended as a single element -- `append(b.lines, nil)` on `[][]cell` -- renders `nil` as `default!`, which C# overload resolution binds to `append`'s `params` parameter as the **whole null array** (a non-expanded params call), appending **ZERO** elements rather than one nil element. This silently no-ops the grow: text/tabwriter's `addLine` never extended `b.lines`, so every `terminateCell` indexing `b.lines[len(b.lines)-1]` panicked with index `[-1]`. The interface (`error`) and named-composite branches above already cast a nil element (its untyped-nil type differs from the element type), but an **unnamed** nillable element type -- slice/map/pointer/chan/func -- matched no branch and emitted bare. The converter now casts any untyped-nil element to the slice's element type, forcing single-element binding:
```csharp
lines = append(lines, (slice<nint>)(default!));       // []int   element
maps  = append(maps,  (map<@string, nint>)(default!)); // map     element
ptrs  = append(ptrs,  (ж<nint>)(nil));                 // pointer element (nil renders in pointer context)
```
A nil element is only ever valid when the element type is nillable, so the cast target always exists. Spread appends (`append(dst, src...)`) are excluded (the existing `Ellipsis.IsValid()` guard). Guarded by the `AppendNilSliceElement` behavioral test (slice/map/pointer element types, output-compared vs Go).

### A struct-literal interface field takes a pointer element's adapter
A composite struct literal whose field is an INTERFACE type, initialized with a POINTER element whose pointer-receiver method set satisfies that interface, must record and route the same `*T`→interface adapter a call argument does — `&handlerWriter{l.Handler(), &logLoggerLevel, capturePC}` (log/slog SetDefault), where field `level` is `Leveler` and `*LevelVar` implements Leveler via a pointer-receiver `Level()`. The struct-field interface routing (`checkStructFields`) recorded/routed a NAMED VALUE element that satisfies the field (`DecodingError{InvalidIndexError(idx)}`) but matched only a `*types.Named` element, so a POINTER element fell through: no `GoImplement<LevelVar, Leveler>(Pointer = true)` was recorded, and the box `ᏑlogLoggerLevel` was passed bare to the interface-typed constructor parameter (CS1503). The detection now takes the concrete satisfying type from the element OR the pointee of a POINTER element (`types.Implements` tested on the element's own pointer method set, the non-interface guard tested on the pointee), so a pointer element records and routes exactly like the value case:
```csharp
new handlerWriter(l.Handler(), new LevelVarжLeveler(ᏑlogLoggerLevel), capturePC)
// [assembly: GoImplement<LevelVar, Leveler>(Pointer = true)]  -- in package_info.cs
```
The record flows through the existing pointer-target arm of `convertToInterfaceType` (the `ж<T>`-wrapped name unwraps to `GoImplement<T, Iface>(Pointer = true)`, and the render wraps the box in the generated `TжIface` adapter), so a same-package local (`streamWriter`→`io.Closer` in net/http/fcgi) and a foreign pointee (`*ast.SelectorExpr`→`ast.Expr`, `*Basic`→`Type`, `*Func`→`Object` in go/types) route through their local or foreign adapters uniformly. Positional and keyed literals both resolve their field (a keyed element renders `d: new SettingжDescriber(Ꮡs)`); an already-interface element and a value element are unchanged. (Guarded by the `PointerInterfaceStructField` behavioral test — a pointer-receiver-only implementer placed in an interface-typed struct field, positional via an addressed global and keyed via an addressed local, output-compared vs Go.)

### The struct-field interface routing also fires on an ELIDED element composite
The routing above lived only on the TYPED composite path (`checkStructFields`, reached from
`convCompositeLit`'s `*types.Named`/`*types.Struct` arms). An **elided** element composite — the inner
`{v0, v1, …}` of a `[]struct{…}{…}` / `map[K]struct{…}{…}` / `[N]struct{…}{…}`, which drops the repeated
struct type and resolves it by inference (`compositeLit.Type == nil`) — took the separate target-typed
`new(…)` constructor branch, which emitted its element values through `convExprList` with **no** interface
recording or routing at all. So a struct field of interface type in such a literal was passed bare: a
POINTER form lost its `new TжIface(…)` adapter wrap, and a VALUE form whose concrete was used *only* in the
elided literal (never converted to the interface anywhere else) was never `GoImplement`-recorded, so no
`partial struct T : Iface` was generated for it. Both compile to **CS1503**. This is exactly errors'
`wrap_test`, whose `[]struct{ err error; … }{ {&poser{…}, …}, {errorUncomparable{}, …} }` produced 17
`cannot convert from 'ж<poser>' / 'errorUncomparable' to 'error'` at the `new(…)` sites while the sibling
`multiErr{poser}` slice-element cast (a *different* path) wrapped its `poser` correctly.

The interface-field record+route loop was extracted from `checkStructFields` into a shared
`recordStructFieldInterfaceCasts(compositeLit, structType, callContext)` and is now called from **both** the
typed path and the elided path (against the inferred `*types.Struct`), so an elided struct composite routes
its interface fields identically:
```csharp
new(new poserжerror(poser), err1, true)                      // *poser  → error  (Pointer = true)
new(new errorUncomparableжerror(Ꮡ(new errorUncomparable(nil))), …)  // *errorUncomparable → error
new(new errorUncomparable(nil), …)                            // value form: partial struct : error boxes
// [assembly: GoImplement<poser, error>(Pointer = true)] + <errorUncomparable, error>[(Pointer = true)]
```
The extracted logic is byte-for-byte the proven typed-path logic (same keyed-vs-positional field resolution,
same value/pointer method-set satisfaction test), so it inherits every guard the typed path already carried
(the gif keyed-field bogus-record avoidance, the `types.Implements` pointee test). An isolated A/B
full-reconvert of a production cross-section (fmt, errors, net/http, encoding/json, flag, go/types, os, time,
text/template — 172 `.cs`) shows **zero** production emission change: the pattern is overwhelmingly a
test-code shape, so the fix is inert for ordinary packages and only realizes the previously-uncompilable test
literals. (Guarded by the `ElidedStructInterfaceField` behavioral test — a pointer-receiver `*pointerErr`
and a value-receiver `valueErr`, each used *only* in an elided `[]struct{ err error; … }{…}`, output-compared
vs Go; the pre-fix converter emits the bare box / bare value and fails CS1503 on both.)

**And on the elided POINTER element composite (2026-07-31).** There are *three* composite paths, not
two: `[]*struct{…}{{…}, …}` — Go's shorthand where the `&` is implied — has its own arm in
`convCompositeLit`, reached before the elided-struct arm above and emitting `Ꮡ(new T(…))` rather than
the target-typed `new(…)`. That arm marked `any` field literals but never called
`recordStructFieldInterfaceCasts`, so a concrete element in an interface slot again reached the
generated constructor bare. net `ip_test`'s `[]*struct{ in IP; str string; byt []byte; error }` — an
**embedded** `error` field — handed a `ж<AddrError>` to the `error` parameter with no
`AddrErrorжerror` wrap (CS1503). The arm now makes the same record+route call its two siblings do:

```csharp
Ꮡ(new ipStringTestsᴛ1(new IP(…), "?0123456789abcdef"u8, default!,
    new net_test_package.net_AddrErrorжerror(Ꮡ(new AddrError(Err: …, Addr: …)))))
```

The tell is worth carrying forward: each of the three paths grew its own field-marking sequence
(`markStringFieldLits` / `markAnyFieldLits` / `recordStructFieldInterfaceCasts`) independently, and the
one that fell behind is the one nobody had a failing case for — the same shape-versus-its-pointer-
composition asymmetry as *An anonymous struct lifts from ANY depth of its declared type*. Behavioral
CNR is byte-identical across the whole corpus: like its sibling, this is a test-code shape. (Guarded
by the `ElidedStructInterfaceField` extension — a `[]*struct{ want string; error }` whose interface
field is **embedded**, carrying both a pointer-receiver and a value-receiver implementer.)

### A keyed element's interface target is the composite's own SLOT, never the LHS variable's type
A composite literal assigned to an **interface-typed** variable converts to that interface as a
WHOLE — `visitAssignStmt`'s `convertExprToInterfaceType` (and `visitValueSpec`'s
`convInterfaceDeclValue` for a declaration) wraps the finished literal in its adapter. `convKeyValueExpr`
*also* consulted the LHS variable's type (`context.ident`) for each keyed element, so the interface was
applied a SECOND time, to values whose real slot is not an interface at all. On a map whose element type
is a POINTER that is silently destructive: the element renders correctly as `Ꮡ(new T(…))`, the spurious
`*T → Iface` conversion adds the deref prefix, and `convertToInterfaceType`'s
"`~` of an immediate `Ꮡ(…)`" collapse then hands back the bare struct — a `map[K]*T` slot holding a
VALUE (CS0029).

os's `TestCopyFS` is the reached case: `fsys` is an `fs.FS` and the test *reassigns* it

```go
fsys = fstest.MapFS{"william": {Data: []byte("Shakespeare\n")}}   // map[string]*MapFile
```

which emitted `["william"u8] = new fstest.MapFile(Data: …)` instead of `Ꮡ(new fstest.MapFile(…))`, ×5.
The same literal in a `var` declaration, as a call argument, or assigned to its own concrete type was
always correct — only the reassignment path carried the LHS type down into the elements, which is the
tell that the LHS was never the right source of truth.

The element's target is now the composite's own value slot (`valueSlotType`, already computed for the
`MapSource`/`StructSource` untyped-constant boxing just above), with the LHS ident kept only as the
FALLBACK for a composite that does not state its slot type here — the sparse-array shape it was
originally added for. A struct FIELD of interface type keeps its single conversion through
`structFieldIfaceType`. An interface-VALUED container (`map[K]Iface{k: v}`) still converts every element,
now through the slot rather than the variable, so the two agree by construction. Behavioral CNR is
byte-identical across the corpus — the shape needs a *named* container of pointers reassigned to an
interface variable, which the behavioral corpus did not contain. (Guarded by the
`ElidedPtrElemIfaceAssign` behavioral test: a `map[string]*Item` and a `[]*Item`, each declared into,
reassigned into, and passed into an interface, with a write through a stored element pointer proving the
map holds the same object; plus an interface-VALUED map as the live control for the preserved
conversion.)

### A GoImplicitConv record needs at least one LOCAL operand
`ImplicitConvGenerator` realizes a recorded conversion as a `partial struct <name>` inside THIS package's
class, so the record has to name a type this package declares. The generator already relocates the host
when exactly ONE side is foreign (its "foreign SOURCE via a local alias" / "foreign TARGET via a qualified
reference" arms), and the converter's aliased-numeric arm swaps target and argument for the same reason —
to anchor the record on the local operand. With NEITHER operand local the swap merely picks the other
foreign one and the generator has nothing to extend: it declares `partial struct <simple name>` locally, a
PHANTOM type of that name, and the operator body's `src.Value` does not exist (CS1061).

os reaches it from `os_windows_test.go`'s privilege helper, `syscall.CloseHandle(syscall.Handle(t))` over a
`syscall.Token` — both operands in `syscall`. Both the struct-conversion and the aliased-numeric arms of
`checkForImplicitConversion` now require `conversionRecordHasLocalOperand`, stated once as the property
rather than per-arm. Declining costs nothing: the call site already emits the explicit
`((syscallꓸHandle)(uintptr)t)` cast chain, which needs no generated operator, and an operator between two
foreign types could not be hosted in either of their assemblies from here in any case. Behavioral CNR is
byte-identical. (Guarded by the `ForeignPairNumericConv` behavioral test — a sibling library declaring two
named numerics and never converting between them, converted across in `main`, with the
foreign→local and local→foreign directions as the live controls for the records that are still needed.)

### Named-string wrapper surface (indexing, sub-slicing, span bridge)
A named type over `string` is indexed and sub-sliced in Go (`tag[i]`, `tag[i:j]` -- reflect `StructTag.Get`), but C# indexing never applies user-defined conversions. The `InheritedType` template therefore forwards the `@string` surface on every named-string wrapper: `byte this[int]` / `byte this[nint]` indexers, a `Range` indexer returning the WRAPPER (a Go sub-slice of a named string keeps the named type), `nint Length` for `len()`, and an implicit `ReadOnlySpan<byte>` operator so `u8`-literal comparisons and assignments bind. Guarded by `NamedStringConversion`.

### A `:=`-declared string local keeps its named type and its heap box
A string-underlying local declared with `:=` takes its EXPLICIT declared type through the same general
declaration path every other type uses (never `var` — a `u8` literal would infer `ReadOnlySpan<byte>`).
The old dedicated string branch hardcoded `@string` as the declared type, which (a) DISCARDED a named
string type — go/types check.go's `fileVersion := asGoVersion(…)` declared its `goVersion` locals as
`@string`, so the `goVersion` extension methods `isValid()`/`cmp()` no longer bound (CS1929 ×4) — and
(b) BYPASSED the escape-analysis heap-box check, so `cause := ""` followed by `&cause` emitted an
unboxed local while the call site referenced the nonexistent box `Ꮡcause` (CS0103):
```csharp
goVersion fileVersion = asGoVersion((~@file).GoVersion);   // named type preserved
ref var cause = ref heap<@string>(out var Ꮡcause);         // escaping local heap-boxes
cause = ""u8;
```
A plain, non-escaping string local emits exactly as before (`@string s = "…"u8;` — the general path's
explicit-type arm resolves to `@string`). The same explicit-type routing applies in the for-init
tuple-declaration form. (Guarded by the `NamedStringDefine` behavioral test — a named-string `:=` with
methods called on the local, an escaping `cause := ""` written through its pointer, and a plain string
local, output-compared vs Go.)

### A typed const of a named string type keeps the named type

The CONST-DECL arm of the same materialization family: `visitValueSpec`'s string-constant emission
hardcoded `@string`, so net/http pattern.go's `const equivalent relationship = "equivalent"` (with
`type relationship string`) emitted `internal static readonly @string equivalent = …` — and every
comparison `rel == equivalent` was then ambiguous, because the `[GoType("@string")]` wrapper and
`@string` convert implicitly BOTH ways (CS0034 ×20 across pattern.cs). A typed string const now keeps
its named type, initializing through the wrapper's `ReadOnlySpan<byte>` implicit operator (the
`StringSurfaceMembers` u8 bridge):

```csharp
internal static readonly relationship equivalent = "equivalent"u8;
```

Function-body typed string consts take the same form (`relationship localRel = "moreSpecific"u8;`);
an UNTYPED string const keeps `@string` (its type is not a `*types.Named`). Full-stdlib footprint:
net/http pattern.cs, traceviewer's `ViewType` consts, and regexp/syntax parse.cs.

**The u8 form is required for EVERY value expression, not just a bare literal.** The rule above got
its `u8` from the literal path, which fires only when the spec's value expression is an
`*ast.BasicLit` — i.e. the `const x T = "…"` spelling. Go's other spellings put a different node
there: a **conversion** (`const opLoad = mapOp("Load")`) is a `CallExpr`, and a folded
**concatenation** (`prefix + "Delete"`) is a `BinaryExpr`. Those fell to the folded-value path and
emitted a plain C# string literal, from which the `[GoType("@string")]` wrapper is *two*
user-defined conversions away (`string`→`@string`→wrapper) — which C# forbids, so the whole
declaration group failed (CS0029 ×9 on sync `map_test.go`'s `mapOp` const block). The folded value
now takes the same `u8` rendering whenever the declared type is named:

```go
const opLoad  = mapOp("Load")            // conversion — CallExpr
const opStore = mapOp("op" + "Store")    // folded concatenation
```
```csharp
internal static readonly mapOp opLoad = "Load"u8;
internal static readonly mapOp opStore = "opStore"u8;
```

A RAW (backtick) value has no `u8`-suffixable verbatim form, so it takes an explicit `(@string)` cast
instead — also a single conversion. A plain `@string` const is untouched (`string`→`@string` is
already single-step), which is what keeps the folded-untyped-const emission byte-identical corpus-wide.
(Guarded by `NamedStringConsts` — package-level and local typed consts compared against values and
each other, a method called on a const, the conversion and folded-concatenation spellings at both
package and function scope, and an untyped const staying plain, output-compared vs Go.)

### A grouped var spec with one multi-result call deconstructs
A grouped `var (name, offset, abs = t.locabs() ...)` spec is not a `:=`, so the assignment tuple machinery never saw it -- the per-name path assigned the WHOLE result tuple to the first name and silently DEFAULTED the rest (time appendFormat read a zero abs; a silent-wrongness class beyond the CS0029 that exposed it). Function-local specs now emit the C# tuple deconstruction, matching the `:=` form; package-level specs use the once-evaluated hidden-field component reads:
```csharp
var (ln, ls) = pair();
```
Guarded by `GlobalTupleVarDecl` (both levels, with a call-count check proving single evaluation).

**The function-local gate asks whether a name has a BOX, not whether it "escapes" (2026-07-31).**
That branch is gated to specs no name of which needs a `ref heap<T>` box declaration, and it read the
raw `identEscapesHeap` flag — which the escape analysis **blanket-sets** for every *inherently*
heap-allocated local (pointer, slice, map, chan, **interface**, **func**), because those are already
references and get no box unless their address is genuinely taken. So the gate rejected specs that are
entirely plain, and every tuple with an interface or func result fell back to the very per-name path
this branch exists to replace:

```csharp
context.Context ctx = context.WithCancel(context.Background());   // the WHOLE tuple  (CS0029)
Action cancel = default!;                                          // silently defaulted
```

`identHasHeapBox` is the predicate that answers the gate's actual question, and it is what the branch
now calls. This is the trap `paramAddressTakenNeedsBox` already documents from the other side — *a
verdict the box gate then refuses leaves `identEscapesHeap` set with no box behind it* — and it stayed
hidden because `(int, string)`-shaped tuples, the ones anyone reaches for when probing, work fine.
net's `var ctx, cancel = context.WithCancel(context.Background())` is the corpus site. (Guarded by the
`GlobalTupleVarDecl` extension — a local `var si, fi = ifaceAndFunc()` returning an interface and a
func, both read back.)

### `string()` of an untyped constant reference hops through the default type
`string(utf8.RuneError)` renders the argument as its cross-package `static readonly` Untyped* wrapper, from which `@string` has no conversion (CS0030). The conversion hops through the constant's DEFAULT Go type first -- exactly Go's conversion semantics; a plain literal is already a C# constant and keeps its direct form:
```csharp
fmt.Println("a" + ((@string)(rune)CrossPkgLib.Sep) + "b");
```
Guarded by `CrossPkgUser` (`string(CrossPkgLib.Sep)`).

### A `:=` from a named untyped constant materializes the default type
`codepoint := unicode.ReplacementChar` must not declare with `var`: the constant renders as its
`static readonly` Untyped* wrapper (`UntypedInt`/`UntypedFloat`/`UntypedComplex`), so `var` binds the
LOCAL to the wrapper type instead of Go's inferred default type, and a later Go conversion like
`string(codepoint)` fails (CS0030 — no `UntypedInt`→`@string` form; go/types conversions.go). The
declaration materializes the Go-inferred default type instead — exactly Go's `:=` typing:
```csharp
rune codepoint = replacementChar;    // NOT `var codepoint = …` (binds UntypedInt)
float64 factor = scale;
```
The gate is an Ident/Selector RHS resolving to a `*types.Const` of untyped NUMERIC kind (int is already
routed to the explicit `nint` form, and string consts to the explicit string path); literals and computed
constant expressions render as plain C# literals and keep `var`. Applies in both the single-declaration
and the mixed-statement paths. (Guarded by the `UntypedConstDefine` behavioral test — untyped rune and
float package constants `:=`-bound then converted/multiplied, output-compared vs Go.)

### A computed untyped float constant materializes at its destination's float width
A named untyped constant can still need its `Untyped*` wrapper because another use demands a different
type. When that name participates in a computed float constant, C# must not evaluate the expression
through the wrapper's arithmetic operators:
```go
const repetitions = 100000
var loopBound int = repetitions
mean := .5 * repetitions
var quarterMean float64 = .25 * repetitions
```
The wrapper form makes C# overload resolution prefer `UntypedInt.operator*`, converting `.5` or `.25`
to an integer and truncating it to zero before the result reaches the float local. The converter instead
folds the exact Go constant expression once at the destination's resolved `float32` or `float64` width.
An explicitly typed destination is visible on the expression itself; for a new `:=` local, go/types keeps
the RHS untyped and records the default type on the declared identifier, so the declaration edge supplies
that width. Both paths reuse the same named-constant fold and leave bare references, non-float constants,
and expressions without a wrapper-emitted named constant unchanged. Guarded by `UntypedConstDefine`;
`hash/maphash`'s 100,000-sample SMHasher avalanche bounds are the corpus witness.

### `complex()` over a NAMED untyped constant pins the element width
golib's `complex` builtin is overloaded on element width — `complex(float32, float32) => complex64`,
`complex(float64, float64) => complex128` — and `UntypedFloat` converts implicitly to **both**. C#
then applies its better-conversion-target rule, which prefers the **narrower** target (`float32`
converts to `float64`, not the reverse), so a `complex128` the Go checker typed as such was silently
constructed at float32 width:

```go
const maxFloat32 = 3.40282346638528859811704183484516925440e+38
over := complex(maxFloat32*2, maxFloat32*2)     // complex128, 6.805646932770577e+38
```
```csharp
var over = complex((float64)(maxFloat32 * 2D), (float64)(maxFloat32 * 2D));
```

Without the casts `over` is `(+Inf+Infi)` — and `encoding/gob`'s `TestOverflow` then found nothing
out of complex64's range to reject, because `float32FromBits` accepts +Inf at either width. A
LITERAL argument never had the problem: the untyped-const analysis records the call's element type
as the argument's context and `convBasicLit` renders the `F`/`D` suffix from it (`complex(1.5D,
2.5D)`). A **named** untyped const (`Δmath.MaxFloat32`), or a constant expression over one, renders
as the `UntypedFloat` symbol and cannot carry a width — so exactly those calls pin their untyped
arguments explicitly, at the element width Go's own typing gives the call
(`complexCallElementType`, resolving an untyped-complex-constant call through its recorded context
and Go's `complex128` default). A MIXED call needs nothing and gets nothing: `complex(g, half)` with
`g` a `float64` was always unambiguous, since `float64` has no implicit conversion to `float32`.

**The rule cannot be expressed from golib's side, and the attempt is instructive.** Naming the
untyped pair explicitly (`complex(UntypedFloat, UntypedFloat) => complex128`) makes every MIXED call
ambiguous — `complex(0D, gHalfPi)` has the float64 overload better on the first operand and the
untyped one better on the second, so neither wins (CS0121). Completing all four width pairings does
not rescue it either: `UntypedFloat` converts implicitly in **both directions** with `float32` and
`float64`, so for an operand that is neither — `complex(7/2, 0D)`, an `int` — no candidate is
strictly better and the ambiguity simply moves. Overload resolution has no way to say "prefer the
width the *call* was typed at"; only the emitter knows that.

Corpus footprint: **zero**. The trigger is a named-untyped-const operand, and no `complex()` call in
the standard library (math/cmplx included) has one — every corpus site is either width-pinned
literals or has a typed operand. Guarded by the extended `ComplexConstContext` (the overflow pair,
its float32-range question, a named-untyped-const pair in both a complex128 and an explicit
complex64 context, and the mixed call that must stay unchanged; neuter-proven — with the arm removed
the guard's `over` prints `(+Inf+Infi)` and `over-fits-float32 true` where Go says `false`).

### A non-escaping `string([]byte)` local emits the stack-string `sstring`
Go elides the copy in `s := string(buf)` when `s` does not escape and `buf` is not observed to change,
letting `s` alias `buf`. `@string` (a heap `byte[]` wrapper) cannot do this — every `string([]byte)` is an
allocation + copy — which is the dominant cost the `PerfString` benchmark measures. The converter therefore
emits, for the provably-safe case, a stack-only [`sstring`](https://github.com/ritchiecarroll/go2cs/blob/master/src/core/golib/sstring.cs)
(a `readonly ref struct` over `ReadOnlySpan<byte>`) that VIEWS the source with no allocation:
`sstring s = ((sstring)buf);` instead of `@string s = ((@string)buf);`. Where the string escapes, the
implicit `sstring`→`@string` conversion copies the bytes to the heap at that boundary.

The escape pass (`markSStringEligible`) records the verdict; it is deliberately conservative — the MVP's
safest idiom only. A local is eligible iff: it is the built-in `string` type bound by a single
`s := string(x)`; `x` is an UNNAMED `[]byte` (a `[]rune`→string must UTF-8-encode — an allocation, no view;
a named `[]byte` would need a two-hop cast C# will not chain); it does not escape by any channel the escape
analysis detects; every use is a safe read — a `len`/`cap` argument, a byte index `s[i]`, or a comparison
against a string literal OR a plain-`string` operand (a variable or field, `s == want`) — so anything else
(passed to a function, stored, ranged, concatenated, RETURNED, reassigned) disqualifies it; and the source
is never written except at its own declaration. (The comparison operand may be any plain-`string`
expression, even a call, because the whole-function "never written" guard already means the source cannot
change; only the built-in `string` type is allowed on the other side — a NAMED string type has no operator
against `sstring`, so it stays `@string`.) Emission is
two coordinated sites: `convCallExpr` retargets the conversion cast to `sstring` (after the Go→C# name
map) under a transient flag; and `visitAssignStmt` declares the explicit type as `sstring` and sets that
flag around the RHS. The comparison literal KEEPS its `"…"u8` `ReadOnlySpan<byte>` form: `sstring` has
zero-allocation comparison operators against `ReadOnlySpan<byte>`, so `s == "x"u8` compares the backing
spans in place. This is what makes the win real — rendering the literal as a plain C# string would force
a `UTF8.GetBytes` allocation on every comparison, and `@string == "…"u8` allocates the literal-as-`@string`
each time (a copy neither the JIT nor Native AOT elides); the `sstring` form is the only zero-allocation
one. Measured: the comparison idiom (`string(buf) == "…"`) runs ~12× faster than `@string` on the JIT and
~11× faster on Native AOT.

Because `sstring` is a `ref struct`, the escapes the predicate does NOT enumerate (storing into a field/
array/map, boxing to an interface, channel send, closure capture) are C# COMPILE errors, not silent bugs;
the two vectors that would be silently wrong — escape via `return` and mutation of the source buffer — are
guarded explicitly.

A second, broader case needs **no escape analysis at all**. An UNNAMED `string(x)` temporary that is an
operand of a comparison is created and consumed *within the single comparison expression*, so it cannot
escape; it is emitted as `(sstring)x` (`markSStringComparisonConversions`, keyed per-`*ast.CallExpr`) as long
as the OTHER operand cannot mutate `x` before the view is read. Three safe shapes qualify (`sstringOtherOperandSafe`):

- a **string literal** (`string(buf[:4]) == "ZLIB"`, `string(item) != "null"`) — the literal keeps its
  `"…"u8` span form and binds `sstring`'s zero-allocation `ReadOnlySpan<byte>` comparison operators;
- a **pure-read plain-`string` expression** — a variable, field, or index read (`string(b[:n]) != magic`,
  `string(word) != "package"`) — which runs no code, so it cannot write the buffer, and compares via the new
  **mixed `sstring`/`@string` operators** (byte-ordinal span compare, no heap copy of either side);
- **another `string(bytes)` conversion** (`string(a) == string(b)`) — both become zero-copy views and compare
  `sstring == sstring`.

It stays `@string` when the other operand could mutate the source before the compare — a **function call**
(`string(a) == next()`: Go's `string(a)` is a copy taken before `next()` runs, but a stack view would be read
only at the `==`, after `next()` could have written `a`) — or when it is a NAMED string type (no operator
against `sstring`). This byte-signature / header-check idiom is by far the most common `string([]byte)` pattern
in the stdlib: the literal form alone reaches ~23 sites, and the plain-`string`-operand and two-conversion
forms extend it further across `crypto/*` (`md5`·`sha1`·`sha256`·`sha512`, comparing against the `magic`
gob-stream prefix), `crypto/tls` (downgrade-canary checks), `hash/*`, `go/internal/*importer`, `html/template`,
and more.

The mixed comparison also widens the **named-local** case above: `s := string(x)` compared against a string
variable or field (`s == want`, `s == cfg.name`) is now eligible, not only `s == "literal"`.

A **`switch string(x) { case … }`** is the same comparison family in statement form
(`markSStringSwitchConversions`). A Go string switch ALWAYS lowers to a single temp assigned the tag value,
then compared against each case label with `==` — an if/else chain, never a C# `switch` and never the
constant-pattern (`is`) form, because string constants render as `static readonly @string` (not a C#
`const`) and literals as `"…"u8`, neither of which is a C# case constant that a `ref struct` could be the
subject of. So `var exprᴛN = ((sstring)x)` infers the stack string and every `exprᴛN == label` binds a
zero-allocation operator (span for a `u8` literal, the mixed operator for an `@string` const/variable).
Because the tag is evaluated exactly ONCE into the temp, the only requirement is that no case label can
mutate `x` before the view is read — every label must be `sstringOtherOperandSafe` (a literal, a pure read,
or another conversion — never a call, which is rejected, and never a named string type, which has no
operator). This covers the common binary-format-detection idiom `switch string(magic) { case elfMagic: … }`,
and applies both to an unnamed tag (`switch string(x)`) and to a named local used as the tag
(`s := string(x); switch s { … }`, where the tag read is added to the named local's safe-use set).

**Concatenation** (`string(x) + suffix`) is the same operand family in a `+` expression. A Go string
concatenation always allocates a fresh result, so the result is a heap `@string` that may itself escape —
only the *operand* is a stack value, and the win is skipping the intermediate `((@string)x)` copy of it.
`golib`'s `sstring` gained `operator+` overloads (against `@string`, another `sstring`, a
`ReadOnlySpan<byte>` u8 literal, and a plain C# `string`, both operand orders, all returning `@string`)
that block-copy the operand span straight into the single result buffer instead. The plain-`string`
overload resolves an otherwise-ambiguous `string + sstring` (both convert implicitly to the other): a
literal in an object/vararg concat context renders without its `u8` suffix (the converter suppresses it),
so `panic("incorrect mantissa: " + string(hm))` (math/big) becomes `"…" + ((sstring)hm)` where `"…"` is a
plain C# `string` — the explicit overload makes it an exact match rather than a CS0034 ambiguity (mirroring
why the comparison form keeps its literal as `u8`). A `string(x)` operand of a `+` is emitted as `sstring`
under the same rules as a comparison operand — `markSStringBinaryOperandConversions` (formerly
`…ComparisonConversions`) now also matches `token.ADD`, and requires the other operand to be mutation-safe
(a literal, pure read, or another conversion — never a call); a named local used in a concatenation
(`s := string(x); s + suffix`) is likewise added to `sstringUsesAreSafe`. `string(a) + string(b)` becomes
`((sstring)a) + ((sstring)b)`, saving both operand copies.

A third refinement is an **optimization**, not a widening of eligibility: **loop-invariant / repeated-conversion
hoisting**. When the same eligible `string(x)` over a never-written source is emitted repeatedly — several
comparison operands, or one inside a loop — the inline `((sstring)x)` re-materializes the view at every use,
and the JIT will **not** hoist a `ref struct` view out of a loop (measured: a non-throwing
`MemoryMarshal.CreateReadOnlySpan` golib view, added so the `ToSpan` bounds check could not block
loop-invariant-code-motion, made *zero* difference and was reverted — the fix must be converter-level). A
per-`FuncDecl` pre-pass (`planSStringHoists`) instead lifts each such group to ONE
`sstring <temp> = ((sstring)x);` at function scope and rewrites every use to the temp (`convCallExpr` returns
the temp name for a lifted `*ast.CallExpr`; `visitBlockStmt` injects the decl before the group's anchor —
the first top-level body statement that contains a use). The safe gate is strong and needs no liveness
analysis: the conversion operand must be a **bare identifier** `x` (never a sub-slice/index — `string(buf[:7])`
and `string(buf[8:12])` are distinct views that must not share one temp), and that `x` must be a plain
function-local or parameter that is NEVER written in the body (`objectIsWritten == false`), declared before the
injection point, with no use inside a nested func literal (a `ref struct` cannot cross a closure boundary —
that is a C# compile error, so the gate keeps the impossibility loud). Worth doing only when the conversion is
genuinely repeated — ≥2 uses, or ≥1 use inside a loop — so a lone comparison stays inline. Real Go-1.23 stdlib `sstring` sites are mostly *single* comparisons where this
is a no-op, so it changes few-to-zero stdlib goldens (the Go-1.23 reconvert hoists **zero** sites — the one
candidate, net/http's `is408Message`, is `string(buf[:7])`/`string(buf[8:12])`, distinct sub-slices the
bare-identifier gate keeps inline); the win is targeted at loop/tokenizer patterns — a scanner comparing
`string(buf)` against several keywords — where a clean back-to-back A/B took `PerfStringView` from ~4.8× → ~3.0×
Go on the JIT (35.9 → 22.5 ms) and ~4.5× → ~1.9× on Native AOT (34.4 → 14.1 ms). That is about the practical
floor within .NET: a decomposition micro-benchmark confirmed the `sstring` `==` operator itself adds *zero*
over a raw span compare — the whole recoverable cost is the per-use view reconstruction, and the residual is
inherent (`SequenceEqual`'s per-call setup on a tiny buffer vs Go's inlined `memcmp`).

Guarded by the `SStringElision` behavioral test — the eligible cases (two eligible locals, an unnamed
comparison operand, two repeated-conversion groups that each hoist to a single reused `sstring` temp — one in
a loop, one straight-line — plus the mixed-comparison additions: a named local compared against a string
variable and against a struct field, and two `string(bytes)` conversions compared directly; plus the switch
additions: a `switch string(x)` with literal cases, a named local as the switch tag, and a magic-constant
switch whose case labels are named `@string` consts; plus the concatenation additions: a named-local
`s + suffix`, an unnamed `string(x) + literal` and `+ variable`, two conversions concatenated, and a concat
into an object context — `fmt.Sprint("v=" + string(b))` — that exercises the plain-`string` `operator+`)
emit `sstring`; source-mutated, print-escaped, and returned locals, a compare-against-a-function-call, a
switch with a function-call case label, and a concat with a function-call operand stay `@string` —
asserting emitted forms and byte-identical Go/C# stdout. Remaining phases (unnamed conversions
passed to non-retaining callees / used as map keys, and a precise per-iteration liveness guard that would
reach the `PerfString` loop) are deferred; see [`docs/Roadmap.md`](Roadmap.md).

## Maps and Channels
Go maps and channels convert to the golib [`map<K,V>`](https://github.com/ritchiecarroll/go2cs/blob/master/src/core/golib/map.cs) and [`channel<T>`](https://github.com/ritchiecarroll/go2cs/blob/master/src/core/golib/channel.cs) structures. `make` becomes a constructor; channel send/receive use the runtime operators:

```go
m := make(map[string]int)
c := make(chan int, 3)
```
```csharp
var m = new map<@string, nint>();
var c = new channel<nint>(3);
```

Map reads honor Go's nil-map and comma-ok semantics (see [Nil and Zero Values](#nil-and-zero-values) and [Multi-Result Values and Comma-Ok Forms](#multi-result-values-and-comma-ok-forms)).

### `m[string(b)]` — the map-READ key does not copy (`tmpstring`)

The Go compiler special-cases `m[string(b)]`: because a map lookup hashes and compares its key but
never retains it, the `[]byte`→`string` conversion's result provably does not outlive the index
expression, and the copy is skipped (`runtime.slicebytetostringtmp`). The converted C# paid that copy
on every probe — one backing `byte[]` per call — which is exactly the allocation
`net/textproto.TestCommonHeaders`' want-ZERO `testing.AllocsPerRun` assert measures over
`canonicalMIMEHeaderKey`'s common-header probe (L11). The converter now recognizes the same shape and
emits golib's `tmpstring(b)` — a TRANSIENT `@string` windowing the slice's live backing through
`@string.TransientAliasOf`, zero allocation:

```go
if v := commonHeader[string(a)]; v != "" { return v, true }
v, ok := m[string(b)]
```
```csharp
@string v = commonHeader[tmpstring(a)]; if (v != ""u8) { return (v, true); }
var (v, ok) = m[tmpstring(b), ꟷ];
```

The scope is deliberately EXACTLY the shape whose safety Go's own optimization proves
(`mapReadTmpStringKey`, `convIndexExpr.go`): a map index in **rvalue** position — plain or comma-ok —
whose key type is the PREDECLARED `string` and whose key expression is a conversion to predeclared
`string` over a plain `[]byte` (element exactly basic `uint8`). Everywhere the string ESCAPES the
copying conversion stays: an assignment target (`m[string(b)] = v` stores the key — emitted
`m[((@string)b)] = v`), `delete(m, string(b))`, the function's own `return string(a)` paths, a
named-string key type, a named-over-byte element. Compound assignments and `++` mark the index an
assignment target, so they keep the copy for both their read and write halves. (Guarded by the
`MapStringBytesLookup` behavioral test — hit/miss/comma-ok probes through a mutating slice, a
sub-slice operand, and the store-then-mutate case proving the STORED key copied — and by
`GolibTests.AllocationCounterTests.TmpStringMapProbeChargesNothing`, which pins the zero charge in
both units.)

### The NIL map key

Go's map accepts **nil as a key** whenever the key type can be nil — `map[any]V`, `map[error]V`,
`map[*T]V`, a named-interface key — and that entry is an ordinary entry: it reads, comma-oks,
overwrites, deletes, counts toward `len`, is visited by `range`, is dropped by `clear`, appears in a
composite literal, and copies through `maps.Clone`. The converter renders it as `default!`, so the
Go and C# sides line up member for member:

```go
m := make(map[any]string)
m[nil] = "nil-key"
v, ok := m[nil]
delete(m, nil)
lit := map[any]int{nil: 1, "b": 2}
```
```csharp
var m = new map<any, @string>();
m[default!] = nilKeyˢ;
var (v, ok) = m[default!, ꟷ];
delete(m, default!);
var lit = new map<any, nint>{[default!] = 1, [(@string)"b"u8] = 2};
```

golib's `map<TKey, TValue>` wraps a `Dictionary<TKey, TValue>`, which **rejects a null key** with
`ArgumentNullException` *before* its comparer is ever consulted — there is no comparer to teach. So
the nil entry gets a slot of its own: the backing store is a private `Dictionary<TKey, TValue>`
**subclass** carrying `HasNilKey` + `NilKeyValue`, and every member of the map surface routes a null
key to that slot (indexer get/set, `Set`, comma-ok, `TryGetValue`, `ContainsKey`, `Add`, `Remove`,
`Clear`, `Count`, both enumerators, `Keys`/`Values`, the copy constructor behind `CloneMap`, and
`ToString`). `range` yields the nil entry ahead of the buckets — Go's range order is unspecified and
deliberately randomized, so the position is free, and every map *without* a nil key stays on the
dictionary's own enumerator unwrapped.

Two design points are load-bearing. First, the slot lives on the **store, not on the struct**: a Go
map is a reference type, so every copy of a `map<K,V>` value must observe the same nil entry, and a
field on the `readonly struct` would make a write through one copy invisible through another.
Deriving from `Dictionary` (rather than wrapping it) also keeps the struct exactly one reference wide
— no extra allocation, no widened value — and leaves every existing Dictionary interop path (the
implicit conversions, the `ICollection<T>` casts, the reflection bridge's backing-field probe)
binding as before. Second, `map<K,V>` is golib's hottest type, so the nil test is
`!typeof(TKey).IsValueType && (object?)key is null`: `typeof(TKey).IsValueType` is a **JIT-time
constant**, so for a value-type key — `map[string]V`, `map[int]V`, the overwhelmingly common shape —
the test and every branch it guards fold away and those instantiations compile to exactly the code
they had before nil keys existed. Only a reference-typed key pays a null check, and the slot
operations themselves sit behind `[MethodImpl(MethodImplOptions.NoInlining)]` so the hot members stay
small. Measured: `PerfMap` (`map[int]int`) is flat — 276.4 ms with the slot vs 271.8 ms without
(median of three 9-run sessions each), inside the 260–310 ms run-to-run band the *unchanged* build
spans on the same machine.

One consumer cannot see the slot and had to be threaded explicitly: `reflect.DeepEqual` walks the
backing `IDictionary` through a reflected field probe, which never yields a nil key, and a lone nil
entry does not necessarily show up in the `Len` comparison either (one extra ordinary key on the
other side hides it). `IMap` therefore exposes a non-generic `NilKeyEntry` — `(present, boxed value)`
— with a default implementation on `IMap<TKey, TValue>` that asks the comma-ok indexer, so the
generated named-map wrappers satisfy it with no go2cs-gen change; DeepEqual compares that entry
before the dictionary walk. (Guarded by `NilMapKey`: set/get/comma-ok/overwrite/delete/`len`/`range`/
`clear` on `map[any]string`, a nil-key composite literal, `map[error]int`, and the nil-key reads on
both a nil and an empty map, all output-compared vs `go run`. Before the fix the very first
`m[nil] = …` threw `ArgumentNullException`, which is how sync's `TestIssue40999` died as an
infrastructure error.)

**PRINTING a nil-key map is a second such consumer, one layer down.** `fmt` orders map keys through
`internal/fmtsort`, which walks the map with `reflect.Value.MapRange` and compares the key Values —
and the reflection bridge typed each entry from its BOXED OBJECT, which for the nil key is `null`, so
`Key()` handed back the **invalid zero Value**. `fmtsort.compare` cannot compare that at all: it reads
`aVal.Type()` first thing and falls through to `panic("bad type in compare: " + aType.String())` on a
nil type, so printing *any* map carrying a nil key died inside `fmt`. Go's rule is the slot rule the
bridge already applies to struct fields and slice elements — **a map entry Value is typed by the map's
DECLARED key/value type**, so `map[any]V` hands out Kind Interface keys whatever the dynamic value is,
and a nil key or value is a VALID nil Value of that type. `MapIter` now carries the map's key and value
types (plus the map Value's read-only bits) and `Key()`/`Value()` build through `makeTypedValue`, which
also makes `fmtsort`'s nil-compares-low rule reachable for the first time — a nil key sorts first,
exactly as Go prints it. (Guarded two ways: `NilMapKey` gains the printing shapes — `map[any]int` with
a nil key through both `Println` and `%v`, a nil-only map, `map[error]int`, `map[*int]int` — and the
new `ReflectMapRangeNilKey` drives the bridge directly over `map[any]int`, a named `map[any]int`,
`map[*int]string`, `map[error]int`, a concrete `map[string]int`, a slice-valued map and a nil
interface VALUE, printing only order-independent facts because Go's map iteration order is
unspecified. Pre-fix it reports `any: 0 0 1 5 3` — zero interface-kind keys, zero nil keys, ONE
invalid key, and a value sum of 5 instead of 6 because the nil entry was skipped — against Go's
`any: 3 1 0 6 3`.)

### An INTERFACE map key compares by Go equality, never by adapter identity

Go compares interface values by (dynamic type, dynamic value), and that **one** relation serves both
`==` and map-key lookup: a `map[Iface]V` finds an entry under exactly the values `==` calls equal. In
the conversion the two had diverged. Emitted `==`/`!=` route through golib's
[`builtin.AreEqual`](https://github.com/ritchiecarroll/go2cs/blob/master/src/core/golib/builtin.cs),
which unwraps the three generated adapter tiers (`IInterfaceAdapter`, `IжAdapter`, `IValueAdapter`)
before comparing — but `map<K,V>`'s backing `Dictionary<TKey,TValue>` used the **default** comparer and
compared the *wrappers*.

That gap is observable because an interface value's wrapper is not stable. The same Go dynamic value is
presented through whichever adapter the static interface currently holding it calls for, so asserting an
`Object` to a narrower `dependency` yields a **different** wrapper object over the same receiver box.
Under the default comparer the asserted value could no longer find its own entry in the map it came out
of — while `==` on the very same pair still answered `true`, because `AreEqual` unwrapped. Equal but
unfindable:

```go
M := make(map[dependency]*graphNode)
for obj := range objMap {                       // objMap is map[Object]*declInfo
    if obj, _ := obj.(dependency); obj != nil {
        M[obj] = &graphNode{obj: obj}
    }
}
for obj, n := range M {
    for d := range objMap[obj].deps { /* ... */ }   // every key of M IS a key of objMap
}
```
```csharp
foreach (var (obj, n) in M) {
    foreach (var (d, _) in (~objMap[obj]).deps) { /* ... */ }
}
```

This is `go/types`' own `initorder.dependencyGraph`, and it is why the converted type checker could not
type-check **any** source: the missed lookup returned a nil `ж<declInfo>`, and `~` on it nil-panicked
one frame later — surfacing through `check.cs:430`, `handleBailout`'s faithful re-panic of Go's own
`default: panic(p)` arm, which is a bailout frame and not the fault site.

The fix is in golib and centralizes on the relation that already existed:
[`GoEqualityComparer`](https://github.com/ritchiecarroll/go2cs/blob/master/src/core/golib/GoEqualityComparer.cs)
projects `AreEqual` as an `IEqualityComparer<TKey>`, hashing the **unwrapped root** so the hash stays
consistent with it — the same rule the compile-time `ImplementGenerator` adapters already applied
(`m_box.GetHashCode()`), which the runtime shells never had. It is installed only for key types that can
actually carry an adapter (`typeof(TKey).IsInterface`, or `any`); a concrete key — `@string`, an
integer, a converted struct — is never wrapped and keeps `EqualityComparer<TKey>.Default`'s
devirtualized path, the test being a JIT-time constant per instantiation. Restating the relation inside
each generated shell was rejected for the reason the defect illustrates: `AreEqual` is golib's single
definition of Go equality, and a second copy per shell class is exactly the drift that produced this.
(Guarded by the `InterfaceAssertionMapKey` behavioral test — pointer- and value-receiver implementors,
an `Object` that is not a `dependency`, lookup after narrowing, lookup after re-widening, and the
`Object(d) != obj` identity probe that pinned the equal-but-unfindable split.)

### Named map types and constrained map access

A defined map type — `type Grades map[string]int` — emits the `[GoType("map[K, V]")] partial struct` forward declaration (completing the long-standing `visitMapType` stub), implemented by go2cs-gen's Map template: full forwarding of `IMap<K, V>` (including the two-value comma-ok indexer), `IDictionary<K, V>`, enumeration, and the `ISupportMake` factory through the wrapped `map<K, V>`. Its composite literal wraps the concrete map literal in the named constructor — `new Grades(new map<@string, nint>{["a"u8] = 1})` — mirroring named arrays/slices (a direct indexer-initializer would target a default wrapper with no backing dictionary; the old emission produced Go-style `key: value` inside C# braces — CS1513). Comma-ok indexing works through a **constrained map type parameter** too: `v, ok := m[k]` where `M ~map[K]V` detects the map CORE of the constraint (both at the assignment's tuple gate and in the index emission) and routes the same `m[k, ꟷ]` two-value indexer, which lives on `IMap<K, V>` itself. The **nil comparison** `m == nil` — Go's only legal map comparison, maps.Clone's nil-preserve guard — emits the `IMap.IsNil` property (`if (m.IsNil)`; backing-store null, distinct from an allocated empty map — no operator exists on a type parameter, CS8761), and `delete(m, k)` on a constrained map binds a golib `delete(IMap<K, V>, K)` overload (key/value types infer from the interface conversion). (Guarded by the `GenericTypeInference` extension `EqualMaps` — a maps.Equal clone over a named map type through the constraint, comma-ok + comparable-erased equality, values vs Go.)
For source-generated named-map wrappers, the generator parses the `[GoType("map[K, V]")]` payload at the top-level comma, not every comma in the string. This matters for function-valued maps: `type opTable map[CrossPkgLib.Ticks]func(int, int) int` emits `map<global::go.CrossPkgLib_package.Ticks, Func<nint, nint, nint>>`, preserving the full delegate as the value type. Any source-file alias used inside the `[GoType]` payload is resolved through Roslyn and rewritten to its fully-qualified target before the template emits `IMap<K, V>`, `IDictionary<K, V>`, and `ICollection<KeyValuePair<K, V>>`; generated files therefore do not depend on file-local package aliases such as `using token = ...`. (Guarded by `NamedMapCrossPkgKey`.)

The named arm must also **carry the map type into the key/value slot emission**, exactly as the
unnamed arm does. Every `MapSource` slot rule in `convKeyValueExpr` is gated on that type: a pointer
KEY or VALUE boxes to `Ꮡx` rather than aliasing a deref'd value into a `ж<T>` slot (CS0029), an `any`
key or value slot re-renders a string literal as `(@string)"…"u8` instead of the bare `u8` span (which
has no conversion to an `object` slot — CS1503), an untyped-constant `any` key boxes at Go's default
type so the store and every lookup agree on the boxed type, and an array-typed key clones into the
map. Left nil, a *named* map type silently opted out of all of them, so `type namedAny map[any]int`
with `namedAny{nil: 1, "b": 2}` emitted `["b"u8] = 2` and failed to compile while the identical
**unnamed** `map[any]int{nil: 1, "b": 2}` one line above emitted `[(@string)"b"u8] = 2`. (Guarded by
the `DeepEqual` behavioral test's named-map block, which is written over `map[any]int` precisely so
the `any`-slot rule is exercised through a named type.)

A bare **`make(Grades)` with no size argument** defaults the size to 0 — emitting `new Grades(0)` — so the wrapper's allocating `(nint size)` constructor runs and the backing dictionary is created. The generated wrapper struct has that `(nint size)` constructor but no *parameterless* one, so a plain `new Grades()` would be `default(Grades)` — a **nil** map (null backing store, so `m == nil` is true and a write panics), whereas Go's `make` returns a **non-nil empty** map (`m == nil` false, writes succeed). The default is applied only to `*types.Named` defined types: the unnamed `map<K, V>` builtin already allocates in its own parameterless constructor and stays `new map<K, V>()`, and a type *alias* (`type M = map[int]int`) resolves to that builtin rather than a wrapper — so neither drifts (`make` emission in `convCallExpr.go`, right beside the named-channel default below). This mirrors the named-channel unbuffered default (`make(closeWaiter)` → `new closeWaiter(1)`); a sized `make(Grades, n)` (already `new Grades(n)`) and the `Grades{}` composite literal are non-nil already. (Guarded by `NamedMapMakeNonNil` — `make` with and without a size, a plain nil `var`, and a composite literal, each `== nil`-compared and output-compared vs Go.)

Two `[GoType]` payload conventions coexist, and the generator's alias substitution must tell them
apart. The map/channel emitters write dotted types in **source-alias form** (`CrossPkgLib.Ticks`,
via `getAliasQualifiedTypeName`), which the substitution above resolves; the slice/array element and
defined-over-selector emitters write the **namespace-qualified form** (`io.fs_package.FileInfo`,
via `getFullyQualifiedTypeName`), which roots through the `go` namespace and must pass through untouched.
The telltale is the segment after the leading identifier: a real alias maps to a package *class*,
so its next segment is a type name — a `_package`-suffixed next segment means the leading
identifier is a namespace segment that merely *collides* with a file alias. net/http's fs.go
aliases `io` while declaring `type fileInfoDirs []fs.FileInfo` → `[]io.fs_package.FileInfo`;
substituting the `io.` produced the nonexistent `go.io_package.fs_package.FileInfo` (CS0426 ×48).
The substitution skips exactly those occurrences (a negative lookahead on `_package.`). On the
converter side, the namespace-qualified form must lead with the **canonical** qualifier, never a
file-local Δ collision-rename: a consumer whose own namespace has a same-named child imports under
`using ΔIoLike = IoLike_package;`, but `[]ΔIoLike.FsLike_package.Info` resolves nowhere in the
alias-free `.g.cs` — `canonicalizeQualifierRename` reverts a leading import-rename segment
(mirroring the visitTypeSpec global-using-target rule). (Guarded by `NamedSliceChildPkg` — a
nested-namespace consumer package importing both `IoLike` and `IoLike/FsLike`, with a named slice
of the subpackage's type used across the assembly boundary.)

A map indexed by a **non-empty interface key** converts a concrete key expression through the same interface-adapter path used by assignments and call arguments. For example, `seen[item] = "kept"` where `seen` is `map[Node]string` and `item` is `*Item` emits `seen[new ItemжNode(item)] = "kept"u8`; the comma-ok read emits the same adapter for the key, `seen[new ItemжNode(item), ꟷ]`. This records the pointer implementation (`GoImplement<Item, Node>(Pointer = true)`) and keeps dictionary lookup semantics aligned with Go's interface key identity. Empty-interface map keys keep their existing literal handling (`map[any]...` turns string literals into Go strings rather than UTF-8 spans), and pointer-typed map keys keep the direct pointer-box path. (Guarded by `InterfaceMapKeyPointer`.)

A **pointer-keyed** map indexed by the method's **receiver** supplies the receiver's box as the key, exactly like the deref-aliased pointer-parameter case: `t.m[c]` inside `func (c *conn) …` emits `t.m[Ꮡc]` (plain read, write, and the comma-ok `t.m[Ꮡc, ꟷ]` alike) — net/http transport.go's idle-connection bookkeeping (`t.idleLRU.m[pc]`) passed the deref-aliased VALUE where `ж<persistConn>` was expected (CS1503 plus the `(v, ok)` deconstruction cascade). The box exists only on a **direct-ж** method, so the receiver-as-map-key body shape itself now *promotes* the method to direct-ж (`bodyUsesReceiverAsPointerValue` gained an `IndexExpr` case, gated on a pointer-KEYED map operand) — a method whose only pointer-use of its receiver is the map key still gets `this ж<conn> Ꮡc`. A pointer LOCAL is unchanged (it *is* the key — no `Ꮡ`), and `delete(t.m, c)` boxes through the ordinary pointer-argument rule once the method is direct-ж. (Guarded by `PtrKeyMapReceiverLookup` — pure-shape promotion, plain read/write, comma-ok, and delete through two distinct receiver identities, values vs Go.)

A value sent into a channel of **non-empty interface element type** converts through the same
interface-adapter path used by assignments and call arguments — the send emission previously tested
the CHANNEL type itself for interface-ness (never true, its underlying is `*types.Chan`), so no
conversion ever fired. A value implementation sends bare while recording the implement pair for the
generator (`vs.ᐸꟷ(new dog(name: "rex"u8))` with `[assembly: GoImplement<dog, speaker>]`); a pointer
implementation wraps the box in its generated pointer adapter:

```go
ps := make(chan speaker, 1)
c := &cat{name: "tom"}
ps <- c
```

```csharp
var ps = new channel<speaker>(1);
var c = Ꮡ(new cat(name: "tom"u8));
ps.ᐸꟷ(new catжspeaker(c));   // records GoImplement<cat, speaker>(Pointer = true)
```

A pointer-typed send value renders as its box (parity with the argument-position rule in
`convExprList`), and a type-parameter element (`chan T` in generic code) keeps the bare emission.
The string-literal empty-interface arm of the same helper is described under
[Empty Interface (`any`)](#empty-interface-any). (Guarded by `AnyStringLitChanSend` — a value impl and a pointer
impl sent through a `chan speaker`, method-dispatched on receive, output-compared vs Go.)

### Named channel types

A defined channel type — `type closeWaiter chan struct{}` (net/http's h2 bundle) — emits the
`[GoType("chan T")] partial struct` forward declaration (completing the long-standing
`visitChanType` stub; the whole corpus previously had NO `GoType("chan …")` — CS0246 at every use),
implemented by go2cs-gen's Channel template: the wrapper holds a `channel<T>` and forwards its full
surface — the Go-visual send/receive members (`ᐸꟷ`, `ꟷᐳ`, including the select-registration
`ᐸꟷ(v, ꓸꓸꓸ)`/`Sending`/`Receiving` forms), the comma-ok `Receive(ꟷ)`/`Received` pair, `IChannel`'s
object-typed members, enumeration for `range`, the `ISupportMake` factory, and a `(nint size)`
constructor so `make(closeWaiter)` emits `new closeWaiter(0)` — a REAL unbuffered channel (the make
path resolves the chan through `Underlying()`, giving named channels the same unbuffered default as
plain `chan T`; the wrapper constructor forwards the size unclamped, so named channels can be
unbuffered — see the channel-runtime section below):

```go
type closeWaiter chan struct{}
func (cw *closeWaiter) Init() { *cw = make(closeWaiter) }
func (cw closeWaiter) Close() { close(cw) }
func (cw closeWaiter) Wait()  { <-cw }
```

```csharp
[GoType("chan EmptyStruct")] partial struct closeWaiter;

[GoRecv] internal static void Init(this ref closeWaiter cw) {
    cw = new closeWaiter(0);
}

internal static void Close(this closeWaiter cw) {
    close<EmptyStruct>(cw);
}

internal static void Wait(this closeWaiter cw) {
    ᐸꟷ<EmptyStruct>(cw);
}
```

Two deliberate wrinkles. **Free-function channel ops name the element type explicitly** —
`ᐸꟷ<EmptyStruct>(cw)`, `close<EmptyStruct>(cw)`: golib's `ᐸꟷ<T>(channel<T>)`/`close<T>(in
channel<T>)` reach the wrapper only through its user-defined conversion to `channel<T>`, which C#
generic inference never considers (CS0411); the explicit type argument lets the conversion apply at
the argument instead (`namedChanElemTypeArg`, applied at the unary-receive, select-registration and
`close` emission sites — a plain `chan T` operand is byte-identical; a package that ALSO
declares a `close` method keeps the `builtin.` shadow qualification of the general builtin path,
so net/http emits `builtin.close<EmptyStruct>(cw)`). **The wrapper's `Close` is an
explicit `IChannel` implementation only**: Go code commonly defines its OWN `Close()` method on a
named channel type (the closeWaiter shape above), and a public instance `Close` would shadow that
method's extension form at every call site; `close(ch)` routes through the golib free function, so
no public surface is lost. (Guarded by `NamedChannelType` — the closeWaiter trio plus a buffered
`type intQueue chan int` exercising make/send/len/cap/receive/comma-ok/close/range/select, output
vs Go.)

### A function-LOCAL named type declaration hoists to member level (slice/map/channel/array/pointer)

C# forbids a type declaration inside a method body, so a `type X []T` / `type X map[K]V` /
`type X chan T` / `type X [N]T` declared **inside a function** cannot emit its `[GoType(…)] partial
struct X;` forward declaration in place — the following statements would then parse as MEMBER
declarations (`CS1519 Invalid token 'foreach' in a member declaration`, `CS1513 } expected`, the
map form's `CS8124`). A local `type X struct{…}` already hoists: `visitStructType`/`visitIdent`/
`visitInterfaceType` each redirect the declaration into `currentFuncPrefix` (emitted at member level
ahead of the method), rename it with the enclosing-function prefix (`ExampleChunk_People`), and
register the lifted name in `liftedTypeMap` so every reference resolves to it. The array/slice, map,
and channel emitters did **not** — they wrote the forward declaration straight into the method body
(the reported slices `example_test`/maps `maps_test` defect). The shared helper `liftLocalTypeDecl`
(`visitTypeSpec.go`) now applies that same hoist to all three: at package scope it is a no-op
(target stays `v.targetFile`, `finish()` does nothing, so production emission is byte-identical),
and inside a function it prefixes the name, registers the lift, redirects to a member-level builder,
and flushes into `currentFuncPrefix`. A local **slice/array of a local element type** also needs the
element resolved to its lifted name: `visitArrayType`'s simple-identifier fast path (which keeps the
written name so `[3]rune` stays `rune`) is skipped when the element is itself a lifted local type
(`!v.liftedTypeExists`), routing it through `getFullyQualifiedTypeName`, which resolves `liftedTypeMap` — so
`type People []Person` (Person a local struct) emits `[GoType("[]ExampleChunk_Person")] partial
struct ExampleChunk_People;`, not the raw `[]Person`. (Guarded by the `LocalNamedTypeDecls`
behavioral test — a function-local named slice-of-local-struct, map, channel, and fixed-size array,
each constructed/ranged/indexed in the body and output-compared vs Go; the unfixed converter leaks
four `partial struct …;` declarations into the method body.)

Two completions of the same rule, both demonstrated by `encoding/gob`'s test suite:

* **The POINTER kind hoists too.** `type X *T` was the one forward-declaration kind still writing
  its `[GoType("ж<…>")] partial class X;` straight into the body — gob's `codec_test.go`
  `type Rec ***Rec` produced `CS1525 Invalid expression term 'partial'` and took the rest of the
  function with it. It now takes `liftLocalTypeDecl` like the other kinds, and the lift is taken
  **before** `convStarExpr` renders the pointer text so a self-referential declaration resolves its
  own name through `liftedTypeMap`.
* **A SELF-REFERENTIAL local type re-resolves its element after the hoist.** The array/map/channel
  emitters resolved the element/key/value name *before* the declaration's own hoist registered its
  lifted name, so `type recursiveSlice []recursiveSlice` / `type recursiveMap
  map[string]recursiveMap` (gob's `encoder_test.go`) emitted `[GoType("[]recursiveSlice")]` on a
  member-level `TestRecursiveSliceType_recursiveSlice` — a name that no longer exists, `CS0246`
  inside the generated slice/map partial. Each emitter now re-resolves its element through
  `liftedTypeMap` **when the hoist actually renamed the declaration**; a package-level declaration
  never renames, so its emission is untouched (verified byte-identical across the whole behavioral
  corpus and the 302-package stdlib).

**Known residual:** a *conversion expression* to a hoisted local named **pointer** type
(`NodePtr(&Node{V: 9})`, with `type NodePtr *Node` declared in the function) still emits the
pre-hoist source name (`new NodePtr(…)`, `CS0246`). The composite kinds do not have this — a local
`Tally(m)` correctly renders `((main_Tally)m)` — so the gap is specific to the named-pointer
conversion arm's target-name resolution. It was previously masked by the hard syntax error above and
has no consumer among the measured packages (gob only *declares* `Rec` and takes its address); the
`LocalNamedTypeDecls` guard therefore uses the assignment form `var np NodePtr = &Node{V: 9}`.

### An embedded field's NAME is the UNQUALIFIED type name (dot-imported embeds)

An embedded struct field's name is, per the Go spec, the *unqualified* type name. A cross-package
embed written as a selector (`struct{ io.Writer }`) already stripped its qualifier for the field
name; a **dot-imported** embed (`import . "io"` then embedded `ReaderFrom`) reaches the emitter as a
bare `*ast.Ident`, yet `getAliasQualifiedTypeName` still renders it package-qualified — and, once the package is a
collision-rename, as `Δio.ReaderFrom`. Gating the qualifier-strip on the *selector* form left that
qualifier in the field name (`internal io_package.ReaderFrom Δio.ReaderFrom;`), whose embedded dot is
a C# syntax error (`CS1003 '(' expected` / `CS1026 ') expected'` — the reported io `io_test`
defect). `visitStructType` now strips to the last segment whenever the resolved embedded-type name
carries a qualifier (covering both the selector and dot-imported-ident forms; a same-package embed
has no dot, so it is a byte-identical no-op), yielding the correct `public io_package.ReaderFrom
ReaderFrom;`. (This is one root among several in the io test suite, which remains blocked by separate
`import . "io"` using-alias resolution issues — the `Δio` namespace is emitted but never aliased.)

### Select statement lowering (terminating and empty clauses)

A `select` lowers to a C# `switch` over a golib runtime call that commits exactly ONE case and returns its ordinal: the blocking form `switch (select(ᐸꟷ(a, ꓸꓸꓸ), …))` (selectgo — commits a uniformly-random ready case or parks), and the default form `switch (trySelect(…))` (the same poll pass, returning -1 so the C# `default:` label runs when no case is ready). Receive cases keep a `case N when selᴛN.ꟷᐳ(out v):` guard that consumes the committed value; send cases are performed by the runtime commit and get a bare `case N:` label. Every case's operands are hoisted into select-scoped temps (`var selᴛN = …;`) emitted in strict source order and evaluated exactly once at select entry — a receive case's channel operand (used by BOTH the registration and the guard) and a send case's whole registration call, so the registration list names only temps (see the operand-evaluation section below). The registration calls (`ᐸꟷ(ch, ꓸꓸꓸ)` receive, `ch.ᐸꟷ(v, ꓸꓸꓸ)` send) return `SelectOp` case descriptors and `select(params SelectOp[])` runs a faithful selectgo (see the channel-runtime section below): it commits exactly ONE ready case — chosen uniformly at random — or parks until one becomes ready. A committed receive's value crosses to the winning case's unchanged guard (`case N when ch.ꟷᐳ(out v):`) through a per-thread pending-frame stack the guard pops (a stack, so a select nested in the guard's target expression cannot destroy the outer commit — see the channel-runtime section), so the emitted select text is identical to the pre-redesign form. Two structural completions (io pipe.go's `read`):

* **An EMPTY clause body still needs its jump.** C# requires every switch section to end in a jump statement (CS8070 on a final `default:`, CS0163 otherwise); the emitted `break;` was suppressed when the *previous* clause ended in a terminal `return` (the was-return flag is reset per statement, and an empty body has none). The flag resets per *clause* now — a bare Go `default:` emits `default: { break; }`.
* **A terminating blocking select gets an unreachable trailing `return default!;`.** Go's spec makes a select with no `default:` whose every comm-clause body ends in a terminating statement itself terminating, so a value-returning function may end with it. The lowered form's guarded `case N when <recv>:` labels cannot prove exhaustiveness to C# (CS0161). Mirroring the switch guarded-terminal-default rule, the emission appends `return default!;` after the closing brace — gated on: no default, every clause terminating (`isTerminatingStmtList`, conservative), no select-targeting `break`, a value-returning signature, and not named-return-defer mode (void wrapper).

The golib non-blocking receive underpinning the default-form guards distinguishes the two "no value" cases per Go semantics: a **closed** empty channel is receive-ready with the zero value; an **open** empty channel reports not-ready, so the `default:` is taken. (Guarded by the `SelectStatement` extensions `firstMsg` — terminal blocking select in a value-returning func — and `poll` — empty `default:` after a returning case, polled both before and after `close`.)

### A NIL channel is never ready — and asking must not throw

golib models a channel as a **struct**, so the nil channel is that struct's ZERO value: every field
is null. Go gives a nil channel well-defined behavior — it is never closed, a receive or send on it
blocks forever, and in a `select` with a `default` the nil case is simply not chosen — so the
readiness probes must *report* "not ready" rather than dereference the absent state. Most of them
already did (`SendIsReady` / `ReceiveIsReady` / `Receiving` all null-check their backing fields);
`IsClosed` did not, so merely asking whether a nil channel was closed threw a
`NullReferenceException`.

This is not an exotic shape. os/exec's `Start` runs

```go
if c.ctx != nil {
	select {
	case <-c.ctx.Done():
		return c.ctx.Err()
	default:
	}
}
```

and `context.Background().Done()` **is** a nil channel, so every child process launched through a
background context crashed in the probe — the last blocker on math/rand's `TestDefaultRace`.
`IsClosed` now reports `false` for a nil channel, which makes the non-blocking receive fall through
to "not ready" and the `default:` clause run, matching Go. (Guarded by `NilChannelSelectDefault`:
nil receive and comma-ok receive taking the default, `len`/`cap` of a nil channel, a real channel
behaving normally alongside, and a mixed select where the nil case must never win over a ready real
case.)

### The default form routes through trySelect — send cases are unguarded in both forms

The `default:` form was originally lowered as `switch (ᐧ)` with per-case try-operation guards
(`case ᐧ when ch.ꟷᐳ(out v):` / `case ᐧ when ch.ᐸꟷ(v, ꟷ):` — the interim fix for the dropped-send
defect os/signal's `process` exposed, where an unguarded `case ᐧ:` ran unconditionally and silently
dropped the value). That shape is single-fire by construction (C# evaluates the ordered guards until
the first true) but its ready-case choice is the **case order**, never uniform-random — provably
unfixable against an ordered C# `switch`. The default form now routes through golib's non-blocking
`trySelect(…)`: the same registrations as the blocking form, the same selectgo poll pass (distinct
cores locked in Id order, Fisher-Yates pollorder, exactly one commit under the held locks), no
parking, and -1 — the default sentinel matched by the C# `default:` label — when no case is ready:

```go
select {
case c <- sig:
default:   // send but do not block for it
}
```

```csharp
switch (trySelect(c.ᐸꟷ(sig, ꓸꓸꓸ))) {
case 0: {
    break;
}
default: {
    break;
}}
```

Send cases get a bare `case N:` label in BOTH forms — the runtime call performed the winning send
itself, so a guard would either send the value a second time or fail and silently skip the chosen
clause body. Receive cases keep their `case N when ch.ꟷᐳ(out v):` guard, which consumes the
committed value from the runtime's per-thread pending-frame stack. Go's remaining rules live in the
runtime: a **closed** channel's send case panics (Go panics even when a `default:` exists — the
poll pass checks `closed` before readiness, so a closed FULL channel panics rather than taking the
default), and a **nil** channel's case is never ready, so it is never chosen. golib's non-blocking
`Sent`/`ᐸꟷ(v, ꟷ)`/`TrySend` surface remains for direct non-blocking sends (and the `-uco=false`
named-method mode), delegating to the same single runtime send implementation.

A pointer-element channel forced two further root fixes, both pre-existing and both previously
unreachable because the dropped send never compiled the value expression. net/rpc's
`func (call *Call) done()` sends `call.Done <- call`: (1) the capture-mode pre-pass had no
send-value position, so the method was never promoted to direct-ж and had no receiver box to hand
out — `bodyUsesReceiverAsPointerValue` now recognizes a `SendStmt` whose value is the pointer
receiver; and (2) `convSendValueExpr` applied the pointer ident context only for *interface*
elements, so a deref-aliased pointer (a pointer parameter, or the receiver) rendered as its value
and could not bind the `in ж<T>` send parameter (CS1503). Both forms of send route through
`convSendValueExpr`, so the statement form `ch <- recv` — broken in exactly the same way — is fixed
by the same change.

(Guarded by `SelectSendDefault`: full buffered taking the default then the same select succeeding
once drained, free-capacity buffered delivering the value, unbuffered with a waiting receiver, nil,
closed-panics-through-the-default, one-ready-among-several, a send and a receive case with neither
ready, exactly-one-send when several are ready, and a no-default select still blocking.)

### Every case's operands are hoisted — evaluated exactly once, in SOURCE ORDER, at select entry

Go's spec evaluates, for every case in the statement, a receive operation's channel operand and a
send statement's channel AND right-hand-side expressions exactly once, in source order, upon
entering the select. Leaving an operand inline in the `select(…)`/`trySelect(…)` registration
argument list breaks that in two distinct ways.

**Evaluated twice.** A receive case's operand appears in the registration call AND again as the
winning guard's receiver, and C# reads a struct method call's receiver AFTER evaluating its
arguments, so even a bare identifier can change under the guard (the out-target expression runs
first). A non-referentially-stable operand — `case <-time.After(d):` (net/http/pprof),
`case <-fresh():`, or an identifier the out-target reassigns — re-evaluates to a DIFFERENT channel:
the runtime's pending-frame core match then (correctly) refuses delivery, and the factory's side
effect runs twice.

**Evaluated out of order.** C# evaluates the registration arguments in argument order, i.e. AFTER
every hoisted temp. A send case left inline therefore had its channel operand and value expression
observed after a *later* receive case's operand: a select whose FIRST case was a send observed
`[recv-chan, send-chan, send-val]` where Go's order is `[send-chan, send-val, recv-chan]`.

The converter therefore hoists EVERY case's operands into select-scoped temps, emitted in strict
source order, leaving the registration list naming only temps. Uniformly, with no stability
analysis — `channel<T>` struct copies share one core, so the temp preserves identity, and the hoist
IS Go's up-front-once evaluation model:

```go
select {
case v := <-fresh():
    ...
}
```

```csharp
var selᴛ1 = fresh();
switch (select(ᐸꟷ(selᴛ1, ꓸꓸꓸ))) {
case 0 when selᴛ1.ꟷᐳ(out var v): {
    ...
    break;
}}
```

A SEND case hoists its **whole registration call** rather than two separate operand temps
(`SelectSendRecvMix`, a send case textually first on a full channel plus a receive case on the
same channel):

```go
select {
case ch <- 8:
    fmt.Println("send fired on full channel (wrong)")
case took = <-ch:
}
```

```csharp
var selᴛ3 = ch.ᐸꟷ(8, ꓸꓸꓸ);
var selᴛ4 = ch;
switch (select(selᴛ3, ᐸꟷ(selᴛ4, ꓸꓸꓸ))) {
case 0: {
    fmt.Println("send fired on full channel (wrong)");
    break;
}
case 1 when selᴛ4.ꟷᐳ(out took): {
    break;
}}
```

That is both legal and stronger than two operand temps. `Sending`/`ᐸꟷ(v, ꟷ)` only BUILDS a
`SelectOp` descriptor — golib's `Sending` is `return new SelectOp(m_core, isSend: true, sendValue:
value);` — and the communication is performed later by the runtime commit inside
`select`/`trySelect`, so moving the call ahead of the switch moves no send. The call evaluates its
receiver then its argument, i.e. channel operand then value expression, contiguously and in source
order: exactly Go's rule. And the value expression keeps its ORIGINAL argument position, so every
implicit conversion the `in T` parameter applies — untyped-constant narrowing to the element type,
interface-adapter wrap, `@string`/`nint` boxing, array clone (see the send-value rules above) — is
preserved by construction, with no new type inference anywhere. A separate value temp would have to
re-render the element type to declare itself, and `var` inference is provably wrong there:
`case bch <- 200:` on a `chan byte` becomes `var t = 200;` — an `int`, which no longer converts to
`byte` at the call (CS1503) — and any divergence in a hand-rendered element type would SILENTLY
change the conversion instead. The whole-call hoist also leaves the `ж<T>`-pointer element case
(net/rpc's `call.Done <- call`) unaffected by construction.

A send case's winning label stays a bare `case N:` — the runtime commit performed the send, so it
carries no guard and nothing re-evaluates.

(Guarded by `SelectOperandOnceEval` for the once-only property — ready and parked call-expression
operands with printed call counters, the reassigned-identifier out-target, and the default form;
counter-proven against the pre-fix emission, which FailFasts on the pending-frame core-match assert.
And by `SelectOperandSourceOrder` for the ordering property — a send case textually first with all
three operand expressions logging their fixed source positions: the blocking form with the receive
winning and with the send winning, a default-form select interleaving send/receive/send with nothing
ready (including an untyped `200` into a `chan byte` and a value boxed into a `chan any`), and the
already-correct receive-first direction as a regression anchor. Every select there is deterministic
by construction — exactly one case can ever be ready — so the uniform-random commit never affects
the output. Counter-proven against the pre-fix converter, which prints
`3:recv-chan 1:send-chan 2:send-val` where Go prints `1:send-chan 2:send-val 3:recv-chan`.)

### Known exposure: marker-shaped USER identifiers can collide with synthetic names

The converter's synthetic-name markers — `ᴛ` (TempVarMarker, U+1D1B: `selᴛ1`, `tupleᴛ2`,
`elemᴛ0`, `iᴛ1`, `initᴛ<name>`, lifted-type `<name>ᴛ1`), `ʗ` (CapturedVarMarker), `Δ`
(ShadowVarMarker), and the rest of the Symbols.cs family — are exotic Unicode LETTERS, legal in
Go identifiers. A Go program that itself declares an identifier matching a generated shape (e.g.
`selᴛ1` used in a `select`, emitting `var selᴛ1 = selᴛ1;`) collides with the synthetic name —
loudly, at C# compile time (CS0128/CS0102), never silently. A general Δ-rename of user
identifiers matching the numbered-temp shape was attempted at the sanitizer choke point
(`getCoreSanitizedIdentifier`) and REJECTED: that choke point also renders the converter's own
synthetic names (loop temps `iᴛ1`, lifted anonymous/named-value types `main_MyBoolᴛ1`,
cross-file anon-struct names), so the blanket rule Δ-renamed synthetic names too and churned
non-select goldens; distinguishing user from synthetic identifiers requires threading origin
through many naming call sites — deliberate sprawl for a trigger that demands typing U+1D1B in
Go source. Accepted as a documented family-wide exposure: the failure mode is a compile error
naming the colliding identifier, and the workaround is renaming the pathological identifier in
the Go source.

### Real channel runtime — the hchan/selectgo port (rendezvous, cap/len, single-fire, uniform-random)

The four long-standing channel-semantics gaps (no unbuffered rendezvous; `make(chan T)` conflated
with `make(chan T, 1)`; a blocking select performing EVERY send case; first-match instead of
uniform-random ready choice) were closed together by rewriting golib `channel<T>` over a faithful
port of Go's runtime machinery (`docs/phase4/DESIGN-channels.md` — the blessed synthesized design;
rendezvous and the select rework land as ONE unit because staging rendezvous first regresses the
legacy `Sending` path):

* **`ChanCore<T>` is the `hchan` analog**: a Monitor lock, a circular `T[]` buffer (null when
  `dataqsiz == 0`), `sendx`/`recvx`/`qcount`, `closed`, intrusive `recvq`/`sendq` parked-waiter
  queues, and a monotonic `Id` (the total lock order for select). The `channel<T>` struct holds only
  a reference to its core, so the zero value is the NIL channel and struct copies share one channel.
  `chansend`/`chanrecv`/`closechan` follow Go's routines exactly, including the buffered-full
  parked-sender head-take/tail-enqueue rotation and drain-before-zero comma-ok (a closed channel
  yields its remaining buffered values with `ok == true` first, then `(zero, false)`).
* **`make(chan T)` emits `new channel<T>(0)`** — capacity 0 is a real rendezvous channel; `cap()` is
  `dataqsiz` and `len()` is `qcount`, so `make(chan T)` vs `make(chan T, 1)` are finally distinct
  (the make default in `convCallExpr.go` covers plain and named channels; the gen Channel template's
  wrapper constructor no longer clamps `size < 1` to 1). A parked operation blocks its goroutine's
  own dedicated thread (`Goroutine.Start`), so parking costs nobody else's capacity and a program
  can park thousands of goroutines at once — the shape `GoroutineParkStorm` guards. golib used to
  queue goroutines on the shared ThreadPool and raise its min-thread floor to
  `max(256, 4 × processor count)` to compensate; both the floor and its premise retired with the
  dedicated-thread executor (`docs/phase4/DESIGN-cooperative-scheduler.md`).
* **Blocking select is a selectgo port behind the unchanged emitted text.** The registration
  methods (`Receiving`, `Sending`, `ᐸꟷ(ch, ꓸꓸꓸ)`, `ch.ᐸꟷ(v, ꓸꓸꓸ)`) return type-erased `SelectOp`
  descriptors — invisible to overload resolution at every emitted call site — and
  `select(params SelectOp[])` partitions out nil channels (never registered), locks the distinct
  cores in `Id` order, scans a Fisher-Yates-shuffled poll order, and **commits exactly one ready op
  under the held locks** (uniform-random single-fire, gaps 3+4); otherwise it parks one
  `SelectState`-linked waiter per case, where a single `winner` CAS is the single-fire authority
  every waker — plain send, plain receive, another select's commit, AND close — must win before
  touching a waiter. Publish-before-signal ordering and park-outside-the-lock discipline throughout.
* **The committed receive value crosses to the guard via a per-thread pending-frame STACK** (not a
  single slot): `select`/`trySelect` push a frame (channel core, value, ok) on a receive commit,
  and the winning guard (`Received`/`ꟷᐳ`) pops exactly the frame whose core matches its own
  channel. A stack because the guard's out-argument TARGET expression is evaluated BEFORE the
  guard call, and legal Go can run another select there (`case a[f()] = <-ch:` where `f()`
  selects) — the inner select pushes and pops its own frames, so the outer commit survives; a
  single slot was destroyed by the inner select's entry (outer value lost, or the next buffered
  value stolen — found by the adversarial verification round). Only receive commits push frames; a
  send-case win touches nothing (a select may have send and receive cases on the SAME channel, and
  clearing would destroy an outer frame mid-nest). Known residual: a panic unwinding between
  commit and consume strands a frame — unbounded under a repeated panic-in-target/recover loop,
  an accepted benign memory residual. The stack must never be CAPPED: live depth is dynamic, not
  textual — one textual select whose out-target expression recurses holds one live frame per
  recursion level, so a depth cap silently drops live outer frames (the `DeepSelectRecursion`
  guard, 100 levels, falsified an attempted depth-64 cap). Frames are never MIS-consumed (every
  consume matches the top frame by channel core); a strand stacked above a live frame makes the
  outer select fire zero cases — the committed value is abandoned exactly as the panic abandoned
  the communication, never delivered wrongly. Debug-only depth warnings, never a process-killing
  assert. With no matching frame the same guards are non-blocking probes, unchanged.
* **A channel may have an OWNING TIMER** (Go's `hchan.timer`), the hook Go 1.23's synchronous timer
  channel needs: `IChannelTimer` is installed by `channel<T>.AttachTimer` before the timer is armed,
  `Capacity`/`Length` report 0 while the owner answers `HidesBuffer` (Go's `chanlen`/`chancap`
  timer-channel branch), and `DrainBuffer()` — Go's `runtime.timerchandrain` — empties the buffer
  without servicing parked waiters, so the owner can REVOKE a value the channel already accepted.
  It is the only sanctioned way to un-send, and only sound for a channel whose producer owns it
  exclusively; `IsUnbuffered` keeps reporting the physical shape. Full semantics under
  [Realizing the runtime TIMER contract](#realizing-the-runtime-timer-contract-sleep--newtimer--stoptimer--resettimer).
* **Close/panic semantics are Go's**: send on closed panics (even from within a select, and even
  when a `default:` exists); close of closed and close of nil panic; a parked select-send woken by
  close panics on its own thread; parked receivers (plain and select) wake with `(zero, false)`;
  range-over-channel terminates on closed-and-drained; `len`/`cap` of nil are 0. A boxed
  `IChannel`'s nil comparison is representation nilness (`channel is null`) — the old
  `{ Length: 0, Capacity: 0 }` pattern would misclassify a live empty unbuffered channel as nil.

(Guarded by `ChannelRendezvous` — cap/len 0, not-ready probes with no counterpart, rendezvous
round-trip, ping-pong alternation; `ChannelCapLen` — buffered fill/wrap/drain, nil/unbuffered
len/cap, comma-ok drain-after-close; `SelectSingleFire` — exactly one delivery among multiple ready
send cases, 100-iteration volume guard; `SelectSendRecvMix` — send+recv cases on the same channel,
one-commit-per-select; `SelectRandomFairness` — both branches of a two-ready select taken over 200
iterations; `CloseWakesBlocked` — close waking parked receivers/senders/selects in both directions
plus the whole panic family; `NilChannelInSelect` — nil cases never ready beside live ready and
parked cases; plus the extended `NamedChannelType` unbuffered named-channel rendezvous and the
pre-existing select/channel suite.)

### An escaping comm-clause binding receives into a temp and heap-boxes at clause entry

A `case result := <-ch:` whose bound variable's address is taken in the clause body — internal/fuzz
`coordinatorLoop`'s `c.crashMinimizing = &result` and `writeToCorpus(&result.entry, …)` — escapes to
the heap, so the body's address-of emission references the `Ꮡresult` box companion (an escaping `:=`
local's form). The comm-clause label emitted only a plain `out var result`, never a box, leaving
`Ꮡresult` undeclared (CS0103 ×2, the last own-errors keeping internal.fuzz red after its CS0234s
cleared). The `when` guard's `out var` slot cannot declare a ref local, so `selectCommBinding`
(`visitSelectStmt.go`) receives into a uniquely-numbered temp and opens the clause body with the
entry-time box pattern proven by the escaping-parameter preamble:

```csharp
case 2 when (~c).resultC.ꟷᐳ(out var resultᴛ1): {
    ref var result = ref heap(resultᴛ1, out var Ꮡresult);
```

The gate is `identHasHeapBox` — the exact predicate the body's `&name` emission uses — so the box is
declared iff it is referenced; alias/box names mirror `convertToHeapTypeDecl` (sanitized analyzed name
for the value alias, raw analyzed name behind `Ꮡ` for the box, matching `boxBaseName`). Both bindings
of the `(val, ok)` form are checked. A non-escaping binding keeps the direct `out var <name>` form
(preserving the shadow-rename render, e.g. `out var errΔ5`), and an ASSIGN-mode rebind of an existing
boxed local already writes through its ref alias — the full-stdlib A/B footprint was exactly
internal/fuzz/fuzz.cs. (Guarded by `SelectEscapeBinding` — escaping binding written through both
directions, escaping `(val, ok)` binding with a field address through the box, and a mixed
escaping/plain select, output-compared vs Go; the pre-fix converter fails it with exactly the
CS0103 `Ꮡres` class. A clause taking ONLY a field address (`&res.value`, no whole-var `&res`) still
copy-boxes — the known assignment-position escape-analysis gap, out of scope here.)

## Generic Constraints
A Go generic constraint becomes a C# `where` clause. Most type-set constraints lift to the matching golib/.NET interface — a `[]T` element constraint to `ISlice<T>`, `[N]E` array-core to `IArray<E>`, `map[K]V` to `IMap<K,V>`, `chan T` to `IChannel<T>` — plus, for operator-bearing type sets, the `System.Numerics` operator interfaces (`IAdditionOperators`, `IComparisonOperators`, …) so the body's `+`/`<`/`==` on the type parameter compile. The Go built-in `comparable` maps to golib's [CRTP](Glossary.md#crtp) `comparable<T>`.

### An array-core constraint `~[N]E` lifts to `IArray<E>`

A type-set constraint whose core is an ARRAY — `func polyAdd[T ~[256]fieldElement](a, b T) T` (ML-KEM's
`ringElement`/`nttElement` share the core `[256]fieldElement`) — must map to `where T : IArray<E>`, NOT
to the operator interfaces the general type-set path would produce. An array is a *comparable* type in
Go, so the operator-set resolver put `Array` in the comparable set and lifted `IEqualityOperators<T, T,
bool>`; the named-array `[GoType]` wrapper (which the converter emits for `ringElement` etc.) does not
implement that interface, so every instantiation failed CS0315, and the interface exposes no array
surface, so the body's `t[i]` (CS0021), `for i := range t` (CS8130 on the index deconstruction), and
`for _, x := range t` (CS1579/CS8183) had nothing to bind against. The fix (`getArrayConstraintElem` in
`constraintOperations.go`, a new branch in `getGenericDefinition`) detects a single-array-core type set,
extracts the element type, and emits `where T : /* ~[N]E */ IArray<E>, new()`. The array wrapper already
declares `IArray<E>, ISupportMake<wrapper>` (the go2cs-gen `Array` inherited-type template), whose
`ref E this[nint]` indexer and `IEnumerable<(nint, E)>` enumeration supply exactly the indexing/ranging
surface the body needs, and the `new()` (appended by the same path) covers `var f T`/`T{}` construction.
Greened `crypto/internal/mlkem768` (census 254 → 255); the reconvert A/B changed only `mlkem768.cs`'s
four constraint lines. (Guarded by `GenericArrayConstraint` — two array-wrapper types over a shared
`~[4]fieldElement` core through a generic function that indexes, index-ranges, value-ranges, and
constructs the type parameter, values vs Go.)

### A single-term pointer constraint `[P *T]` erases the parameter to `ж<T>`

A type parameter constrained to a single, non-tilde pointer term — go/types' flat-copy helper
`func clone[P *T, T any](p P) P { c := *p; return &c }` (predicates.go) — cannot be modeled as a C#
type parameter: no C# constraint fixes a parameter to a *specific constructed type*, and `ж<T>`
implements no interface through which `*p`/`&c` could be expressed generically. The operator-lift
fallback emitted `where P : /* *T */ IEqualityOperators<P, P, bool>, new()` with the deref dropped
(`c = p` — CS0029 P→T) and the box mismatched (`return Ꮡc` — CS0029 ж<T>→P), and the call site's
synthesized `clone<ж<ΔSignature>, ΔSignature>(asig)` failed CS0311 (ж<> implements no
IEqualityOperators).

The Go spec makes the faithful lowering an *identity*, not an approximation: a non-tilde term's type
set is a **singleton**, so `P`'s only permissible type argument is `*T` itself. The converter therefore
**erases** such a parameter (`pointerCoreConstraint` in `constraintOperations.go`): it leaves the
emitted `<…>` list and `where` clauses (a breadcrumb comment preserves the Go constraint), renders as
`ж<T>` everywhere it appears (a `getAliasQualifiedTypeName` arm beside the `*types.Pointer` arm), and the parameter
classification treats a `p P` exactly like a `p *T` (`paramPointerType` — deref alias, `Ꮡ` box naming),
so the entire existing pointer machinery applies unchanged:

```csharp
internal static ж<T> clone<T>(ж<T> Ꮡp)
    /* where P : *T (erased: P renders as ж<T>) */
{
    ref var p = ref Ꮡp.Value;

    ref var c = ref heap<T>(out var Ꮡc);
    c = p;
    return Ꮡc;
}
```

Call sites drop the erased position from any synthesized explicit type-argument list
(`renderedTypeArgs`, applied at convCallExpr's two synthesis blocks and convSelectorExpr's
method-group form): `clone(asig)` emits `clone<ΔSignature>(asig)`, and a callee whose remaining
parameters make C# inference sufficient stays bare (`setThrough(Ꮡn, 55)`). An EXPLICITLY written Go
instantiation equally drops erased positions — full (`setThrough[*int, int](…)` →
`setThrough<nint>(…)`), partial (`clone[*thing](…)` → bare `clone(…)`, the rest inferring), and the
function-VALUE form (`fv := clone[*thing, thing]` → `var fv = clone<thing>;`) — via
`explicitTypeArgsAfterErasure` in convIndexExpr/convIndexListExpr. A C# consumer calls the emitted
method naturally — `T` sits in a real parameter position, so inference works without spelling the
phantom `P`.

The pointer classification flips at every use shape, not just the deref/address pair: returning the
parameter WHOLE yields its box (`return a` → `return Ꮡa;`), passing it onward to another erased
callee — including self-recursion — supplies the box (`cloneChain<T>(clone<T>(Ꮡp), …)`; the
interface-shaped argument arm carves out erased params exactly like instantiated pointers), copying
it into a local is a Go pointer copy (`q := p` → `var q = Ꮡp;`, writes through `q` land in the
caller's referent), and a nil comparison takes the box form over the nil-deferring entry alias
(`if p == nil` → `ref var p = ref Ꮡp.DerefOrNull(); … if (Ꮡp == nil)` — a nil argument reaches the
guard instead of throwing at entry, e.g. `orZero[*int, int](nil)`). The NAMED constraint-interface
spellings — `[P PtrOf[T]]` and the embedded `[P interface{ PtrOf[T] }]`, where
`type PtrOf[T any] interface{ *T }` — resolve to the identical singleton type set and erase
identically. The constraint interface's own DECLARATION follows the existing constraint-interface
convention (`[GoType] partial interface PtrOf<T> { /* Type constraints: *T */ }`): a pointer term is
a type-set term, not an embeddable interface (previously it emitted an interface inheriting the
struct `ж<T>` — CS0527), and a GENERIC constraint interface carries its own `<T>` list, so the
arity-0 `<ΔT>` marker list and its generated operator machinery are both suppressed for it.

Erasure is deliberately gated to the identity case: **function** type parameters whose constraint
type-set is a single non-tilde pointer term. Declined shapes warn instead of silently mis-emitting —
an approximate `~*T` admits *named* pointer types, which emit as `[GoType("ж<E>")]` wrapper classes
(not identity with `ж<E>`); pointer unions have no single identity; and erasing a generic *named
type's* parameter would change its emitted arity at every use. None occur anywhere in the converted
stdlib (exhaustive GOROOT census: go/types' `clone` is the only compiled occurrence of the pattern;
see `DESIGN-pointer-core-typeparam.md` on the fix branch for the full study). (Guarded by
`PointerCoreConstraints` — clone/read/write/round-trip through `[P *T]` and the swapped-order
`[T any, P *T]`, flat-copy independence verified, values vs Go.)

### An integer named-numeric wrapper implements the integer operator interfaces

A `[GoType num:]` wrapper (`type stringID uint64`) already declared the *common* numeric operator
interfaces so it could serve a `cmp.Ordered`-shaped constraint (`IAddition`/`ISubtraction`/
`IMultiply`/`IDivision`/`IEquality`/`IComparison`/`IIncrement`/`IDecrementOperators`), but the
*integer-only* three — `IModulusOperators`, `IBitwiseOperators`, `IShiftOperators<T, int, T>` —
were deliberately left off because their operators (`%`, `&|^~`, `<<`, `>>`) are kind-gated. That
left a named integer type unable to satisfy a converter-emitted `~integer` operator constraint:
internal/trace's `type dataTable[EI ~uint64, E any]` instantiated with `type stringID uint64` was
CS0315 ×48 on exactly those three interfaces. The `NumericTypeTemplate` operators already exist
(same kind-gate), so `InheritedTypeTemplate` now also *declares* the three integer interfaces for an
integer underlying (float/complex keep only the common set). `IShiftOperators` additionally requires
`operator >>>` (unsigned right shift) — added to the integer operator block; Go emits no `>>>`, but
the member is needed to satisfy the interface. Cleared internal/trace's 48 CS0315 (49→1, the residual
being the unrelated ΔLabel CS0542). Guarded by `NamedNumericOperatorConstraint` (a generic
`mix[K ~uint64 | ~int32]` applying modulus/bitwise/both-shifts on the type parameter, instantiated
with a named `uint64` and a named `int32`, values vs Go). Corpus-verified against math/big (Word),
archive/tar, and time (Duration).

### Lifted shift constraint uses the BCL shape `IShiftOperators<T, int, T>`

The lifted Integer operator set constrains shifts as `IShiftOperators<T, int, T>` — the shift **count** is `int`, not the type parameter. Every [BCL](Glossary.md#bcl) binary integer implements exactly that shape (`IShiftOperators<TSelf, int, TSelf>`); only C# `int` itself happens to also satisfy the self-typed form, so the self-typed constraint made every non-`int` instantiation fail (CS0315 — strconv's `bsearch[S ~[]E, E ~uint16 | ~uint32]` on `ushort`/`uint`). The shape is also exactly what emitted bodies need: the converter coerces every shift count to `int` (`x << (int)(k)`), so a generic body can only ever perform `T << int`. The generated named-constraint interface template (`Integer` in go2cs-gen) and its dynamic-conversion placeholder shift operators use the same `int`-count shape, keeping the two emitters consistent. (Guarded by the `GenericTypeInference` extensions `bsearchLike`/`halve` — `~uint16 | ~uint32` instantiations with a shift on the type parameter, values vs Go.)

### Builtins over constrained slice type parameters

golib's builtins carry **interface-typed overloads** so a value held as a constrained type parameter (`S ~[]E`, boxed to its `ISlice<E>` constraint) binds directly: `copy(ISlice<T1> dst, ISlice<T2> src)` (plus an `ISlice<byte>`/`@string` form), `clear(ISlice<T> s)`, and two-argument `min`/`max` constrained on `IComparisonOperators` (Go's `cmp.Ordered` lifts to operator interfaces; a constrained `E` has no `IComparable<E>` conversion). The box wraps the same backing array, so interface writes land in the caller's storage — `copy`/`clear` into an `S` are true write-throughs (span windows, memmove semantics for overlap). Overload resolution keeps concrete calls on the exact `slice<T>` overloads (an exact parameter beats a boxing conversion), so nothing outside generic bodies changes. Cleared ~37 of the slices package's constraint seams. (Guarded by the `GenericTypeInference` extension `CopyClearMinMax` — copy into and clear through constrained values, write-through verified by value vs Go.)

**S-preserving sub-slice and append.** Go's sub-slice of a named slice type yields the *same named type sharing the same backing* — pdqsort's recursion depends on it (`pdqsort(s[:mid])` with `s S`). The `ISliceWrap<TSelf, T>` static-abstract factory (`TSelf Wrap(in slice<T> source)`) supplies the non-copying reconstruction: `slice<T>` implements it as identity, every generated named-slice wrapper wraps the window in its own type, and the `~[]E` where-clause carries it (`ISlice<E>, ISupportMake<S>, ISliceWrap<S, E>`). A sub-slice of a constrained type parameter emits golib's `subslice<S, E>(s, lo, hi)` (type arguments explicit — `E` is constraint-only) which routes `S.Wrap(new slice<E>(s.Slice(…)))`; the new `slice<T>(ISlice<T> view)` constructor SHARES storage (unboxes a `slice<T>`, reconstructs any other implementer from its source array and window). `append` on a constrained value binds golib's `append<S, T>(S, params ReadOnlySpan<T>)` (S from the first argument, T from the span — fully inferrable) and wraps the result back to S; its body routes to the core `slice<T>.Append` directly, since a recursive `append(…)` call would resolve back to itself (`slice<T>` satisfies the constraints). The same change fixed the named-slice WRAPPER template's sub-slice members, which routed through `ToSpan()` — *detached copies*, a silent write-through divergence for named slice types generally; they now route through the wrapped `m_value` (sharing). (Guarded by the `GenericTypeInference` extensions `SumHalves` — recursion over sub-slices of S with a write through the deepest view, verified against the caller's array — and `AppendKeep`.)
Every generated named-slice wrapper also implements the non-generic `IArray` surface explicitly. The public typed `Source` remains `T[]` for the concrete wrapper, but the interface member is emitted as `Array IArray.Source => ((IArray)m_value).Source!;`, matching golib's `IArray.Source` contract and keeping `len(IArray)`, element-address helpers, and interface-typed builtins bound to the wrapper. Pointer elements use the same form, e.g. `type queue []*item` emits `ISlice<ж<item>>` plus the explicit `Array IArray.Source` member. (Guarded by `NamedSlicePointerElements`.)

**S where `[]E` is expected.** Go assignability lets a named-slice-typed value pass where the unnamed `[]E` is expected (`rotateRight(s[m:i], …)`, `pdqsortOrdered(x, …)`); the converter materializes such an argument through the SHARING `slice<T>(ISlice<T>)` constructor — `pdqsortOrdered(new slice<E>(x), …)` — a cast cannot apply (interface-constrained source; C# forbids user conversions from interfaces). The constructor unboxes a boxed `slice<T>` directly and otherwise takes the implementer's full-window interface sub-slice, which every golib implementer returns as a boxed shared `slice<T>` — NOT `Source`, which materializes a detached copy (caught by the write-through gate: the helper's write must land in the caller's array). The 3-index form on a constrained value emits `subslice3<S, E>`, and a constrained spread (`append(s, v.ꓸꓸꓸ)` — a `Span<E>`) binds an exact `params Span<T>` twin of the constrained append (betterness otherwise picked the legacy `params T[]` candidate with `T = Span<E>`, a ref struct as type argument — CS9244). (Guarded by the `GenericTypeInference` extension `PassSlice` — S passed to a concrete `[]E` helper, write-through verified by value vs Go — and by the `ConstrainedSliceParamInPlace` behavioral test, which drives a *full in-place mutation* through the materialized `slice<E>` — an element reversal and a real insertion sort mirroring `slices.Sort`/`SortStableFunc` and `internal/fmtsort`'s make+append-built `SortedMap` — over plain, named, and `[]string` sequences, asserting the caller observes the reordering. A detached copy would leave the caller's slice untouched.)

**Explicit `[]E(x)` conversion of a `~[]E` type parameter.** The *explicit* twin of the assignability case above — Go that spells out the slice conversion (`reverse([]E(x))`, `x` of type `S ~[]E`) rather than relying on assignability — took a different converter path and was broken (CS1503). `isTypeConversion`'s `*ast.ArrayType` arm rejects it (a type parameter's `Underlying()` is its constraint *interface*, not `[]E`, so the identical-underlyings gate fails), so it fell through to the general call assembly and rendered `slice<E>(x)` — the golib *array-only* builtin `slice<T>(T[])`, which the `ISlice<E>`-typed source `S` cannot satisfy. The converter now intercepts this shape in the general call path (mirroring the sibling `string|[]byte`-union `[]byte(x)` special case at `convCallExpr.go`): when the conversion target is a slice-type literal and the sole argument is a `*types.TypeParam` with a `~[]E` slice core (`typeParamSliceCore` — the *same* recognizer the implicit path uses), it emits the SHARING `new slice<E>(x)` constructor, so explicit and implicit `~[]E`→`[]E` conversions land identically on the sharing ctor and preserve Go's slice-conversion aliasing. Genuine conversions are untouched: named-slice casts (`[]CaseRange(special)`) take the `isTypeConversion` cast path; string/nil sources are not type parameters; the `string|[]byte` union is handled by the block just above (`typeParamSliceCore` is nil for it). Proven output-neutral (all 1696 stdlib `.cs` byte-identical across an old-vs-fixed reconvert; the behavioral corpus unchanged). (Guarded by the `ConstrainedSliceParamInPlace` behavioral test's `explicitReverseSeq` case — `reverse([]E(x))` over plain and named `~[]E` sources, which did not compile before the fix.)

**An untyped-int literal in a `~[]E`-locked type-parameter slot is cast to the resolved element type.** A bare untyped-integer literal passed where the parameter is the **element type parameter `E` of a sibling `~[]E`-constrained type parameter** — `Index[S ~[]E, E comparable](s S, v E)` called `Index(s, 2)`, or the variadic element of `Insert[S ~[]E, E any](s S, i int, v ...E)` called `Insert(b, len(b)-1, 0)` (the `slices` shape) — drives C# generic inference from the *literal's own C# type*. Go infers `E` from `S`'s core type (`[]int` → `E` = Go `int` → `nint`), but C# has **no analogue for `~[]E` core-type inference**: the emitted `where S : ISlice<E>` does not flow `S`'s concrete element to `E`, so C# infers `E` SOLELY from the value literal. A bare C# int literal is `System.Int32`, so `Index(s, 2)` with `s []int` made C# infer `E=int`, and `slice<nint>` then failed the `~[]int` constraint — `CS0315` (no boxing conversion `slice<nint>`→`ISlice<int>`), `CS0411` (inference failed), or `CS1503` (arg conversion). go/types has already resolved the literal to `E`'s instantiation (`Info.Types[lit].Type` — `int` for the `[]int` caller, `byte` for a `[]byte` caller, `int64`→`long`, `uint`→`nuint`, …), so `convCallExpr` emits the literal AT that C# type via the shared `castArgToType` plumbing: `Index(s, (nint)(2))`, `Insert(b, len(b) - 1, (byte)(0))`. The **sibling-lock gate** (`typeParamIsSliceElementOfSibling`) is what keeps the footprint minimal and correct: the cast fires ONLY when the parameter's type parameter is the slice-element of another type parameter's `~[]E` constraint — the one shape C# cannot infer. Everything C# already infers correctly is left with its bare literal: a **freely-inferred** type parameter (`First[T any](v ...T)` — `T=int32` satisfies `any`, and the value is identical), one **determined directly by another argument** (`setThrough[P *T, T any](p P, v T)` — C# infers `T` from the pointer), and an **explicitly-instantiated** call (`NewOption[nint](42)` — the type argument is already pinned). A resolved **`int32`/`rune`** kind is skipped even inside the gate (a bare int literal already IS `System.Int32` — the `[]int32` element case stays a plain literal), and a value `convBasicLit` already casts (`(nint)…L` for an out-of-int32 constant) is not double-wrapped (the `wholeExprIsCastOfType` skip in `convExprList`). The literal-constant test reuses `isUntypedNumericConstArg`, the same recognizer the `append`-element and narrow-int casts key off, so a *tightened* local const — already declared at its concrete type — is excluded. This unblocks the whole `slices`-package `Index`/`Insert`/`Replace`/`Contains`-family value-argument seam (cleared the entire CS0315 cluster in the `slices` Phase-4 test host — 53→40 residual errors, the remainder unrelated classes). Proven zero-drift on the behavioral corpus. (Guarded by the `GenericUntypedIntArg` behavioral test — `Index`/`appendAll` over `[]int`, a named `numbers []int`, `[]byte`, and `[]int32` element types with bare int-literal args, which did not compile before the fix; the `[]int32` case proves the no-cast arm.)

**Range-over-func on named/generic Seq types.** Go 1.23's `for v := range seq` (and the two-value `for k, v := range seq2`) on an `iter.Seq[E]`-shaped value emits through golib's yield-adapting `range()` overloads. Three pieces make the named/generic form work: detection unwraps the type's `Underlying()` (a defined or instantiated func type is a `Named`, not a bare `Signature`); a NAMED func type renders as a C# *delegate*, which has no conversion to the overloads' `Action<Func<…>>` parameter — its method GROUP does, so the emission appends `.Invoke`; and because C# cannot infer a type parameter from a method group's parameters, the element types are spelled out from the yield signature: `foreach (var v in range<nint>(countdown(5).Invoke))`. `break` inside the body ends the foreach, which cancels the adapter's producer — the yield function receives `false`, matching Go's semantics; a two-value `range<K, V>` overload adapts pair-yields onto the tuple machinery. One adjacent gate was refined en route: a call's result being a generic instantiation adds explicit type arguments only for conversions and GENERIC callees (`NewOption<nint>(42)` — an untyped-const arg would infer C# `int` where Go infers `nint`), never for a plain function returning a generic named type (`countdown<nint>(5)` was CS0308). (Guarded by the `GenericTypeInference` extensions — a generic `Seq[V]` ranged with `break` and a two-value `KVSeq[K, V]`, values vs Go.)

**An EXPLICITLY-instantiated generic function through a package selector renders its type arguments once.** Go's `pkg.Func[T](…)` is an `IndexExpr` (or `IndexListExpr` for `pkg.Func[K, V]`) whose base `X` is the selector `pkg.Func`. `convIndexExpr`/`convIndexListExpr` renders the `[T]` as `<T>` itself. But the base is *also* a generic-function value, so `convSelectorExpr` — which spells a generic function's inferred type arguments when it appears as a method-group **value** (the `slices.SortFunc(all, slices.Compare)` path, needed because C# can't infer a method group's type parameters) — appended `<T>` a second time, producing `pkg.Func<T><T>()`. Depending on context this surfaced as CS1525 (`reflect.TypeFor[X]()` → invalid expression term), CS0119 (a plain-return generic like `saferio.SliceCap[T]`), or CS8124 (`<T>()` parsed as a one-element tuple) — ~67 errors across encoding/gob, xml, asn1, json, text/template, database/sql/driver, debug/macho·pe·elf, and unique. The index expression now converts its base with a `suppressGenericTypeArgs` context flag, so `convSelectorExpr` skips the value-path append when it is the base of an explicit instantiation (the standalone method-group-value case is unchanged — no flag, still appends). A *local* generic function (`Func[T]()`, base is an `Ident` not a selector) never hit this, since only `convSelectorExpr` appends. (Guarded by the `CrossPkgUser` extension — `CrossPkgLib.Wrap[int](5)` (IndexExpr) and `CrossPkgLib.Pair[string, int](…)` (IndexListExpr), both rendering single type-argument lists, output vs Go.)

### Integer type-parameter conversions route through golib (the `E(100)` family)

C# has no cast to or from a type parameter, so the Go conversions in `rand.N[Int intType]` — `Int(x)`, `uint64(n)` — and an untyped constant compared against the parameter (`n <= 0`, which Go types AS `Int` but C# leaves as `int`, unacceptable to the lifted `IComparisonOperators<Int, Int, bool>`) all failed (CS0030/CS0019). Three coordinated pieces, gated on a constraint whose every type-set term has an **integer underlying** (`typeParamIsInteger`): a conversion **to** the parameter emits golib's runtime-typed `ConvertToType<Int>(…)` (typeof-dispatch that JIT-folds to a single branch per instantiation; signed kinds sign-extend, unsigned zero-extend — Go's exact conversion semantics; a `[GoType("num:*")]` wrapper instantiation falls back to a reflection-cached bridge over its `Value` property/ctor); a conversion **from** the parameter to a basic integer emits `ConvertToUInt64<Int>(n)` (plus a plain numeric cast when the target is not `uint64`); and a **constant operand** of a binary op against the parameter materializes via `ConvertToType<Int>(0)` — except a SHIFT count, which Go types independently and the emission already coerces to `int`. Result: `if (n <= ConvertToType<Int>(0)) … return ConvertToType<Int>(ConvertToUInt64<Int>(n) / 2);`. (Guarded by the `GenericTypeInference` extension `halveN` — `~int32 | ~int64` with the compare, both conversions, and a negative value proving sign-extension, values vs Go; clears math/rand/v2's `N`.)

### A named-wide-integer or type-parameter slice index casts to `nint`

Go permits any integer type as a slice/array index, converting it to `int` for the access. The C#
`slice<E>`/`array<E>` indexer takes `nint`, and the existing wide-*basic* index cast already routed
`uint`/`uint32`/`uint64`/`uintptr`/`int64` through `(nint)`. Two more index kinds need it, both from
internal/trace's `dataTable`:
- a **named type over signed `int64`** — `type ProcID int64` indexing `spans[procID]` (CS1503).
  There is no `this[long]` overload, `int64→nint` does not narrow implicitly, and `int64→ulong`
  (which would bind `this[ulong]`) is a signed→unsigned conversion — so it has no bare path.
  `(nint)(procID)` composes as one *user* conversion (named→long) plus one *built-in* (long→nint).
  Every other kind is deliberately **excluded** — no churn, and casting some would even break: an
  *unsigned* named type (`type kindT uint`/uint32/uint64) binds the golib `this[ulong]` overload
  bare, and a nuint-backed wrapper (`uint`/`uintptr`) is *CS0030* under a `(nint)` cast; an
  `int`/`int32`/`nint` underlying narrows implicitly (`type rank int` stays bare). So only signed
  `int64` both *needs* and *accepts* the cast;
- a numeric **type parameter** — `dataTable[EI ~uint64]` doing `d.dense[id]`. A constrained type
  parameter has no C# cast at all, so it routes through golib's `ConvertToUInt64<K>` bridge (the
  `E(100)` integer-type-param family above) and then narrows: `d.dense[(nint)(ConvertToUInt64<EI>(id))]`
  (an arithmetic index `id/8` is still `EI`, so it wraps the same way).

Cleared ~11 of internal/trace's index CS1503/CS0030 (17→6). The companion **shift-*count*** case —
`1 << (id % 8)` where the count is a numeric type parameter, coerced to `int` by `intCastOperand`
(the same coercion the shift-width machinery uses) — routes through the same bridge:
`(uint8)1 << (int)(ConvertToUInt64<EI>(id % 8))` (a bare `(int)(EI)` is CS0030). Cleared internal/trace's
last two shift-count CS0030 (6→4). Guarded by `NamedNumericSliceIndex` (a generic `lookup[K ~uint64]`
indexing by the type parameter and an arithmetic result, a `pick` indexing by a named `int64`, a
`num:nint` `rank` index that must stay bare, and a `bitset[K ~uint64]` shifting by the type parameter,
values vs Go).

### Method-set interface constraints bind the interface directly; pointer instantiations project through the adapter

A type parameter constrained by a **regular method-set interface** (a pure method set, no type-term
unions — go/ast's `walkList[N Node](v Visitor, list []N)`) emits `where N : Node` against the
arity-0 emitted interface. Only union+method **constraint interfaces** take the generic CRTP form
(`ConstraintTest1<ΔT>`); the method-set arm previously emitted the phantom `Node<N>, new()`, which
was doubly wrong: `Node` is emitted arity-0 (CS0308), and the instantiation may itself be an
*interface* (walkList takes `N=Stmt/Expr/Spec/Decl`), which can never satisfy `new()`. The
interface-typed and interface-inheriting instantiations then satisfy the constraint natively
(`Stmt : Node` is emitted inheritance).

A **pointer** instantiation (`walkList(v, n.Names)` with `N=*Ident`) cannot: the `ж<Ident>` box
does not implement the interface — its generated pointer adapter does. The call site projects the
slice element-wise through the adapter, instantiating `N` as the interface itself:

```csharp
internal static void walkList<N>(Visitor v, slice<N> list)
    where N : Node
{
    foreach (var (_, node) in list) {
        Walk(v, node);
    }
}
// call site, N=*Ident:
walkList(v, widen<ж<Ident>, Node>((~n).Names, elemᴛ1 => new IdentжNode(elemᴛ1)));
```

golib's `widen<T, TWide>(slice<T>, Func<T, TWide>)` copies the slice HEADER only — elements alias
the original objects through the shared boxes, so method calls through the projected slice mutate
the real objects. A callee that reassigned `list[i]` itself would not write back; the projection
targets the read/widen shape (Go itself performs the same per-element interface widening inside
the loop). `convertToInterfaceType` supplies the adapter reference and the `GoImplement` recording,
exactly as at scalar `*T→iface` call sites. (Guarded by `GenericInterfaceConstraint` — pointer,
interface, and embedded-interface instantiations of a method-set-constrained generic, calling a
constraint method on the parameter and widening walkList-style, values vs Go; clears go/ast's
CS0308, the go/* toolchain gate.)

### A SELF-REFERENTIAL generic method-set constraint uses a box-wrapping proxy as the type argument

The widen-to-the-interface escape above works only when the constraint interface is **non-generic**
(`N=Node`, so `N` can *be* `Node`). crypto/elliptic's `nistCurve[Point nistPoint[Point]]` — where
`nistPoint[T]` is a **generic, self-referential** method-set interface (`Add(T,T) T`,
`SetBytes([]byte) (T,error)`, …) — cannot: you can't substitute `Point = nistPoint<Point>` (infinite
regress), and the golib box `ж<P224Point>` cannot *nominally* implement `nistPoint<ж<P224Point>>`
(it is a sealed golib type in another assembly, and Go's structural satisfaction has no C# analog).
Four coordinated pieces make it convert **and dispatch**:

1. **The constraint interface is emitted GENERIC.** A method-set interface whose own Go type
   parameter is used in its member signatures carries its `<T>` (and constraints) in C#, exactly like
   a generic struct — `[GoType] partial interface nistPoint<T> { … T Add(T, T); (T, error) SetBytes(slice<byte> _); }`.
   Without it the declaration is arity-0 yet the constraint that references it spells the arity-1
   `where Point : nistPoint<Point>` (CS0308) and every bare `T` is undefined (CS0246). (Go's
   operator-only constraint interfaces are arity-0 *in Go*, so this is disjoint from the `<ΔT>`
   operator machinery.)

2. **One GENERIC adapter class** implements the outer interface: `nistCurveжCurve<Point> : Curve,
   IжAdapter where Point : nistPoint<Point>` wrapping `ж<nistCurve<Point>>` — NOT a class per
   instantiation. The converter's `GoImplement` records are per-instantiation but all resolve to the
   open form here, so `ImplementGenerator` de-dups on the open `(struct, interface)` pair and forwards
   its type parameters and the struct's own constraint (`GetGenericConstraintClause`). The converter
   composes the reference name+args separately (`nistCurveжCurve<…>`, base+`ж`+iface, then the closed
   args) so the type arguments do not bake into the identifier (the old `nistCurve<…>жCurve` was CS1526).

3. **A self-referential PROXY stands in for the type argument.** For each concrete pointer type used
   to instantiate the generic (`nistCurve[*P224Point]`), the converter renders the type argument as a
   generated proxy `P224PointжnistPoint` (element-simple+`ж`+iface-simple) instead of the box `ж<P224Point>`,
   and records `[assembly: GoImplement<P224Point, nistPoint<P224Point>>(ConstraintProxy = true)]` (the
   interface's own argument is a placeholder). `ImplementGenerator.EmitConstraintProxy` emits:

   ```csharp
   internal sealed class P224PointжnistPoint : nistPoint<P224PointжnistPoint>, IжAdapter {
       private readonly ж<P224Point> m_box;
       public P224PointжnistPoint(ж<P224Point> box) => m_box = box;
       public static implicit operator P224PointжnistPoint(ж<P224Point> box) => new(box);
       public static implicit operator ж<P224Point>(P224PointжnistPoint proxy) => proxy.m_box;
       // T rewritten to the proxy itself; the implicit conversions marshal every T-boundary:
       P224PointжnistPoint nistPoint<P224PointжnistPoint>.Add(P224PointжnistPoint a, P224PointжnistPoint b) => m_box.Add(a, b);
       (P224PointжnistPoint, error) nistPoint<P224PointжnistPoint>.SetBytes(slice<byte> b) => m_box.SetBytes(b);
       // …
   }
   ```

   The proxy implements the interface **over itself**, so `Point = P224PointжnistPoint` satisfies
   `where Point : nistPoint<Point>` (CS0311 otherwise) *and* resolves every `p.Add(…)`/`newPoint().SetBytes(…)`
   call inside `nistCurve`'s body. The implicit `ж<P224Point>`↔proxy conversions do all the T-boundary
   marshalling automatically: each forwarder is a bare `m_box.M(args)` (arguments unwrap to the box on
   the way in, results rewrap to the proxy on the way out — including element-wise inside a
   `(T, error)` tuple), and a value flowing into a `Point`-typed position (`base: Ꮡ(new P224Point(…))`)
   converts implicitly at the site. The proxy forwards to the element's **exported** `ж`-extensions even
   cross-assembly (`m_box.SetBytes` binds nistec's extension from crypto/elliptic).

4. **A `func()`-typed field's method-group initializer is re-wrapped as a lambda.** `nistCurve`'s
   `newPoint func() Point` becomes `Func<P224PointжnistPoint>`, but a method group (`newPoint: nistec.NewP224Point`,
   returning `ж<P224Point>`) cannot convert to it — a C# method-group conversion does not apply the
   user-defined implicit operator (CS0407). Inside a constraint-proxy composite the converter re-wraps
   such an initializer as a lambda, `newPoint: () => nistec.NewP224Point()`, whose *return* position
   does apply the conversion.

(Guarded by `GenericPointerInterfaceImpl` — a self-referential `curve[Point point[Point]]` implementing
`Curve` via pointer receiver, instantiated two ways, with a `newPoint func() Point` field and a
`(T, error)`-returning constraint method, values vs Go. Embedding the constrained generic and greening
the whole crypto-curve family is the next subsection.)

### A struct embedding the constrained generic promotes its members — three residual crypto-curve fixes

crypto/elliptic's `p256Curve struct { nistCurve[*nistec.P256Point] }` — a **non-generic** struct
embedding a **concrete instantiation** of the self-referential-constrained generic above — must PROMOTE
`nistCurve`'s internal fields (`newPoint`, `params`) and methods (`Add`/`Double`/`Params`/`ScalarMult`/…)
onto `p256Curve`, exactly as an embed of a plain struct does, so `p256.params = …` binds and the generated
`p256Curve→Curve` interface adapter can forward `curve.Add(…)` to the promoted shim. Because the type
argument is the box-wrapping proxy of the previous subsection, its rendered name **embeds the marker glyph
`ж`** (`nistCurve<P256PointжnistPoint>`) — the thread that runs through all three fixes that made the whole
crypto-curve family (elliptic, ecdh, nistec) COMPILE (+3 packages):

1. **The proxy marker glyph `ж` is not a pointer prefix.** The generator's simple-name / underlying-name
   helpers (`GetSimpleName`, `GetUnderlyingTypeName`) detected a pointer type `ж<T>` by scanning for a
   *bare* `ж` and slicing from it. The proxy's own name embeds that glyph mid-identifier
   (`P256Point`**`ж`**`nistPoint`), so an embed typed `nistCurve<P256PointжnistPoint>` was mis-sliced into
   garbage (its simple name became `oint.Value`, its underlying name an unresolvable string) and the embed
   promoted **nothing** (CS1061 on `params`, CS1929/CS1501 on every forwarded method). Both helpers now
   match the pointer prefix as the two-character `ж<`, so a marker embedded in an identifier is left intact.

2. **A generic-instantiation embed resolves to its declaration and substitutes its type arguments.** An
   embed of a generic INSTANTIATION (`nistCurve<P256PointжnistPoint>`) resolves to the generic DECLARATION
   (`nistCurve<Point>`) by base-name + arity (`FindStructDeclaration` — an instantiation can never
   string-match a declaration that carries its type PARAMETERS), and a generic struct's extension methods
   now match on the type-parameter-bearing receiver (`nistCurve<Point>`, not the bare `nistCurve`). The
   promoted field and method signatures are harvested from the declaration, so they carry its type
   PARAMETER (`Func<Point>`, `pointFromAffine` returning `(Point, error)`); the template rewrites each to
   the instantiation's type ARGUMENT before emission —

   ```csharp
   internal ref global::System.Func<P256PointжnistPoint> newPoint => ref nistCurve.newPoint;
   internal static (P256PointжnistPoint p, error err) pointFromAffine(this ref p256Curve target, ж<bigꓸInt> Ꮡx, ж<bigꓸInt> Ꮡy)
       => target.nistCurve.pointFromAffine(Ꮡx, Ꮡy);
   ```

   — so no promoted member references the out-of-scope `Point`. (The member ACCESS hop keeps the bare
   property name `nistCurve`; only the emitted TYPE is substituted.) When the ENCLOSING struct is itself
   GENERIC — `wrapped<T>` embedding `tag<T>` (the `GenericStructFields` guard) — the promoted method is
   a GENERIC extension method carrying the struct's own type parameters (`static T show<T>(this wrapped<T>
   target) => target.tag.show();`, the substitution then an identity `T`→`T`), else the `T` in the
   receiver and return is an undefined type name (CS0246).

3. **The constraint proxy imports its element's package namespace.** The proxy forwards each interface
   method to the boxed element's box extension methods (`m_box.Bytes()`), which live in the element type's
   PACKAGE class (`nistec_package`, namespace `go.crypto.@internal`). The `[assembly: GoImplement<…>(ConstraintProxy = true)]`
   attribute driving the proxy sits in `package_info.cs`, whose usings never cover a FOREIGN element, so the
   forwarders bound nothing (`ж<P224Point>` "has no `Bytes`", CS1929/CS1501). `EmitConstraintProxy` now emits
   `using <element-namespace>;` for the box element's namespace.

4. **An open-generic interface cast is CONVERTED but not RECORDED.** Inside a generic method the receiver
   itself is cast to the interface — crypto/ecdh's `return newBoringPrivateKey(c, …)` with `c *nistCurve[Point]`.
   The converter must still WRAP it in the generic adapter (`new nistCurveжΔCurve<Point>(Ꮡc)` — the adapter
   the CLOSED per-instantiation records already generate), but must NOT RECORD it as an implementation: a
   record emits `[assembly: GoImplement<nistCurve<Point>, ΔCurve>]`, whose type-PARAMETER argument `Point`
   is out of scope in an assembly attribute (CS0246). `convertToInterfaceType` now skips the record for an
   open-generic target while still firing the adapter-wrapping conversion.

(Fix 2 is guarded by the `GenericEmbedPromotion` behavioral test — a non-generic struct embedding a concrete
`curve[*p224]` over a self-referential proxy: reading a promoted internal field, calling a promoted method
whose parameter is the type argument (passed the promoted proxy-typed field), and reaching the promoted
methods through a non-generic interface adapter, values vs Go. Fixes 3 and 4 need a cross-package element /
a generic-method interface cast the single-package baseline cannot express; they are validated by the census
— elliptic, ecdh, and nistec now emit their DLLs, 254 → 257 packages.)

### Constraint-only type parameters need explicit type arguments

Go infers a type parameter that appears only in *constraints* through core types — `func Twice[S ~[]E, E Integer](s S)` infers `E` from `S`'s underlying element; the `slices` package's whole `Sort[S ~[]E, E cmp.Ordered] → pdqsortOrdered` chain relies on this. C# never infers a type parameter that does not appear in the parameter list (CS0411 — at *every* call site, concrete instantiations included). When the callee declares such a constraint-only type parameter, the converter renders the call's type arguments explicitly from the instantiation `go/types` already resolved (`info.Instances`): `Twice<Point, int32>(p, 2)` at a concrete site, `Scale<S, E>(s, c)` inside a generic body. Calls to generics whose every type parameter is argument-visible keep their bare Go-shaped form — C# infers them as Go does, no churn. (Guarded by the `GenericTypeInference` extension — a constrained `S`/`E` pass-through chain plus a concrete call to a constraint-only-param generic, values vs Go; clears the 14 CS0411s in the slices/maps wave.)

The same explicit-type-argument rule applies to a generic function referenced as a **method-group value**, not just a call. `slices.SortFunc(all, slices.Compare)` (runtime/pprof) passes `slices.Compare[S ~[]E, E cmp.Ordered]` as `SortFunc`'s comparison delegate; C# cannot infer a generic method group's constraint-only `E` when converting it to `Func<…>` (CS0411). `convSelectorExpr` now spells the arguments on the selector — `slices.Compare<slice<uintptr>, uintptr>` — when the selector is NOT the callee of a call (`!context.isCallExpr`, so convCallExpr's own type-arg site still owns the call form) and `info.Uses[Sel]` is a generic function with an `info.Instances` instantiation. Byte-identical across the behavioral corpus and across an A/B of pprof+slices+sort+maps+cmp+net+go/types (a single line moves — the `slices.Compare` argument; every `Compare(...)` **call** stays bare). GUARD OWED — the shape needs a cross-package generic function with a constraint-only type parameter passed as a method-group value, which the single-package baseline cannot express; the bare-IDENT variant (a same-package generic func passed as a method group) is a parallel latent case left unfixed because `convIdent`, unlike `convSelectorExpr`, carries no call-vs-value flag to gate against double-emitting a direct call's arguments.

### The `comparable` constraint

Go's built-in `comparable` admits every `==`-able Go type — numerics, strings, pointers, channels, and comparable structs/arrays/interfaces. No C# constraint can express that set: golib's old `comparable<T>` CRTP interface was implemented by *nothing* (every real instantiation failed — `maps.Keys[M ~map[K]V, K comparable]` could not be used at all), and lifting `IEqualityOperators` would reject structs, which Go admits. A `comparable` type parameter therefore emits **no C# constraint** beyond the standard `new()` — `where K : /* comparable */ new()` — relying on the two facts that make it sound: Go's checker already validated every instantiation, and emitted equality on type parameters routes through `AreEqual`, never operator `==`.

`AreEqual` itself is not a performance tax on that path: a generic overload `AreEqual<T>(T, T)` — automatically preferred by overload resolution exactly where both operands share the type parameter — takes `EqualityComparer<T>.Default.Equals` for value-type arguments, which the JIT specializes per type and devirtualizes to the type's own `IEquatable<T>` (operator-comparable speed, no reflection or boxing; golib wrappers emit `operator ==` and `Equals` as consistent pairs, so semantics match). Reference/interface type arguments delegate to the reflective `AreEqual(object, object)` overload, preserving its typed-null and runtime-type semantics. (A constraint-differentiated overload pair is not expressible — C# treats `where` clauses as outside the signature, CS0111 — and a source-generated `==` twin is unnecessary given the `EqualityComparer<T>.Default` JIT intrinsic.) (The behavioral `GenericVariadicFunc` golden captures the erased form with unchanged output.)

**Floating-point equality follows Go's IEEE-754 `==`, not `Equals`.** The `Equals`-based fast path
above is wrong for exactly one family: `double`/`float` report `NaN.Equals(NaN)` as *true* (and
`Complex`/golib `complex64` inherit that componentwise), while Go's `==` — the operation `AreEqual`
stands in for — is IEEE: NaN compares unequal to everything, itself included. That inverted every
generic NaN probe of the `x != x` form: `cmp.isNaN` emits `!AreEqual(x, x)`, so `cmp.Less` lost its
NaN-first ordering (sort's `TestFloat64s` — `Float64s` → `slices.Sort` produced a NaN-scrambled
order) *and* `cmp.Compare` reported NaN equal to everything, which let the mis-sort slip past
`TestSortFloat64sCompareSlicesSort`'s own equality check. The generic overload now special-cases
`double`, `float`, `complex128`, and `complex64` to the operator compare (JIT-constant `typeof(T)`
guards, box-cast elided). The reflective object overload (boxed/interface comparisons) was already
IEEE-correct and stays untouched: on .NET 7+ the primitives DECLARE `op_Equality` (the
`IEqualityOperators` implementation), so its cached operator lookup finds the real operator rather
than falling to `Equals`. Concrete (non-generic) float comparisons were always correct — `f != f`
emits the C# operator directly. (Guarded by the `ReverseSortNaNOrder`
behavioral test — generic `isNaN`/`less`/`eq` legs over `float64`/`float32`/`complex128`/
`complex64`, boxed-`any` NaN equality, and a NaN-aware interface sort, values vs Go.)

### Generic struct equality is decided per FIELD, not per type parameter
A generic `[GoType]` struct's synthesized `Equals` (see [Struct Types](#struct-types)) was gated on
the struct's TYPE PARAMETERS: unless every parameter carried an `IEqualityOperators`-implementing
constraint (and, stricter still, every *constraint* of every parameter implemented it), the whole
struct's `Equals` body was the constant **`false /* missing equality constraints */`**. Since a
`comparable` parameter deliberately emits no C# constraint beyond `new()` (previous subsection),
essentially every generic struct in the corpus — all 22 generic `[GoType]` declarations at the time
of the fix — carried a constant-false `Equals`, breaking equality that never depended on the
parameter at all. `unique.Handle[T]`'s only field is `*T` (`ж<T>`), whose pointer-identity `==` is
valid for **every** T, yet no two handles ever compared equal — directly contradicting the type's
documented contract ("two handles compare equal exactly if the values used to create them would");
`internal/weak.Pointer[T]`'s only field does not mention T at all. Even
`internal/trace.dataTable[EI, E]` — whose `EI` explicitly lists `IEqualityOperators` — failed the
every-constraint quantifier because `IAdditionOperators` and its siblings do not *themselves*
implement the equality interface.

The gate now decides **per member** (`GetEqualityFallbackMembers` in
`StructDeclarationSyntaxExtensions`): a member whose type supports `==` independent of the
unconstrained parameters keeps the same `this.f == other.f` compare a non-generic struct emits, and
only a member whose type IS an unconstrained type parameter falls back to golib's
**`AreEqual`** — the identical routing the converter emits for Go `==` on any type-parameter
operand, giving `EqualityComparer<T>.Default` speed on value types while preserving IEEE float
semantics (raw `EqualityComparer` reports NaN equal to itself, inverting Go — see the
floating-point note above) and typed-null/runtime-type semantics for reference and interface
instantiations. Real emissions (from the converted stdlib's generated sources):

```csharp
// unique.Handle<T> — ж<T> has pointer-identity == for every T:
public bool Equals(Handle<T> other) =>
    this.value == other.value;

// database/sql.Null<T> — mixed: the T field routes through AreEqual, the rest keep ==:
public bool Equals(Null<T> other) =>
    global::go.builtin.AreEqual(this.V, other.V) &&
    this.Valid == other.Valid;

// net/http.mapping<K, V> — golib slice/map fields carry their own ==, so no member falls back:
public bool Equals(mapping<K, V> other) =>
    this.s == other.s &&
    this.m == other.m;
```

The member classifier asks only "does `==` COMPILE for this member type": a type parameter
qualifies through ANY `IEqualityOperators`-implementing constraint (matching C# operator
resolution, not the whole-struct gate's every-constraint test); reference types (classes incl.
`ж<T>` and `unsafe.Pointer`, interfaces, arrays, delegates), enums, pointers, and built-in value
types always qualify; a `[GoType]` struct qualifies by its **attribute** — both struct templates
emit a same-type `operator ==` unconditionally, and for a struct of the *same compilation* the
attribute is the only visible evidence, because that operator does not exist yet while the
generator runs; any other value type qualifies only by actually declaring a same-type
`op_Equality`. Structs that passed the old whole-struct gate, and every non-generic struct, emit
byte-identical bodies to before — the fallback set is computed only when the gate fails.
`GetHashCode` needs no matching change (`golib.HashCode.Combine` always compiled and hashes
consistently with both compare forms). (Guarded by the `GenericStructEquality` behavioral test —
the `Handle` pointer-identity shape, the plain-T fallback shape, a T-independent-field struct, the
`Null`-shaped mix, a nested generic struct field, and a generic struct as a map key, all
output-compared vs Go.)

### A generic struct implementing an interface BY VALUE partials at its OPEN definition
A Go method on a generic type is declared for every instantiation, so `func (g G[T]) M()` makes
`G[int]`, `G[string]` and `G[G[int]]` all satisfy an interface with `M`. The converter records a
`[assembly: GoImplement<…>]` per instantiation it sees, and `ImplementGenerator`'s value-form arm
wrote one `partial struct` per record, spelled with the record's TYPE ARGUMENTS:
`partial struct G<IntPtr> : I`. C# reads that argument list as a **type-parameter list**, so the
declaration disagrees with the converter's own `partial struct G<T>` (CS0264) and the mismatched
parts stop merging — every member the template writes then lands in the containing **static**
package class instead (CS0715 on the operators, CS0708 on `Equals`/`GetHashCode`/`ToString`,
CS0563 and CS0540 in the cascade). The arm now emits ONE partial against the open definition, keyed
by `(OriginalDefinition, interface)` so all instantiations of a pair fold into it; the member and
value-pair dedupe indexes key on the same open form, since two interfaces over one open generic
share a single partial. Constraints are deliberately omitted — a partial declaration may leave them
off and they merge from the converter's declaration, so omission can never raise CS0265. The
pointer-adapter arm had always done this (`emittedGenericPointerAdapters`, crypto/elliptic's
`nistCurve[Point]`); this is its value-form sibling.

Behind it sat a second, independent defect in the shared `GetSimpleName` helper, and it is the one
that explains why the two packages holding this class both name their generic with a **single
letter**. Asked to drop a type-argument list, the helper tested `typeName.IndexOf('<') > 1` — so
`G<T>`, whose `<` sits at index 1, kept its arguments. `StructTypeTemplate` derives the constructor
name from that call, and emitted `public G<T>(NilType _)`, which is not a constructor to C#: the
`partial struct G<T>` scope never opens and the same spill follows. Every multi-character generic
in the corpus (`meta<T>`, `nistCurve<Point>`, `Handle<T>`) cleared the guard, which is why this
survived to the first single-letter one. The guard is now `> 0` and indexes the *simple* name
rather than the full one — the latter also closes a latent, currently unreached miscut on a dotted
generic (`a.Map<K, V>` indexed at 5 into an 8-character `Map<K, V>`, yielding `Map<K`).

Measured on `internal/reflectlite` (`type B[T any] struct{}`) and `runtime/debug`
(`type G[T any] struct{}` with `var dummy I = G[int]{}` and `var dummy2 I = G[G[int]]{}`), the two
packages the board recorded behind one CS0715 root. `runtime/debug` moves from build-blocked to a
measured **2 of 9**; `internal/reflectlite` clears this root and stops on five unrelated ones.
Guarded by the `GenericValueInterfaceImpl` behavioral test — a single-letter generic held as an
interface at three instantiations, a sibling type named exactly like the type parameter (the
`runtime/debug` shape that made the spilled members render as `debug_test_package.T`), struct
equality, struct-versus-interface comparison, and interface dispatch over a mixed slice, all
output-compared against `go run`.

### The `string | []byte` union
C# generic constraints are conjunctive ("and"), so they cannot express Go's `string | []byte` union directly. The two members share no operators (the union is neither comparable nor additive), so a conforming body may only use the read operations common to both — indexing, `len`, and sub-slicing. These are captured by the golib read-only byte-sequence interface [`IByteSeq`](https://github.com/ritchiecarroll/go2cs/blob/master/src/core/golib/IByteSeq.cs), which both `@string` and `slice<T>` implement; the converter emits it for the union and suppresses the (spurious) lifted operator constraints:
```go
func HashStr[T string | []byte](sep T) uint32 { /* uses sep[i], len(sep) */ }
```
```csharp
public static uint32 HashStr<T>(T sep)
    where T : /* string | []byte */ IByteSeq<T, byte>, new()
{ /* … */ }
```
The constraint is **self-referential** — the C# rendering of the CRTP shape, `IByteSeq<TSelf, T> : IByteSeq<T> where TSelf : IByteSeq<TSelf, T>`, instantiated at the type parameter itself. `@string` implements `IByteSeq<@string, byte>` and `slice<T>` implements `IByteSeq<slice<T>, T>`, so the sub-slice indexer returns the **concrete** sequence type rather than the interface. That extra type parameter exists purely to keep the union allocation-free (next subsection).

`len` resolves through a `len<TSeq>(TSeq) where TSeq : IByteSeq` overload that is dispreferred for concrete `slice<byte>`/`@string` arguments, so existing call sites keep their specific overloads (no ambiguity). Go's `[]byte(s)` and `string(s)` over a constrained value render as the golib extensions `s.ToSlice()` and `s.ToGoString()`, each generic over the caller's concrete type; both preserve Go's per-instantiation semantics exactly (`[]byte([]byte)` shares its backing, `[]byte(string)` copies, `string(string)` is free, `string([]byte)` copies). The behavioral test `StringByteUnionConstraint` exercises both the `string` and `[]byte` instantiations across all of these.

#### Allocation-free union-constrained bodies
The interface was originally single-parameter (`IByteSeq<T>`) with an `IByteSeq<T> this[Range]` sub-slice indexer. Every member whose signature named the sequence type therefore forced a **box**, and Go bodies over this union sub-slice constantly:

| Emitted shape | Boxed | Why |
|:--|--:|:--|
| `((bytes)(s[a..b]))` | 48 B (`slice<byte>`) / 24 B (`@string`) | the range indexer returned the INTERFACE, so the struct result was boxed — and the cast then unboxed it |
| `len(s)` | one box per call | the overload took `IByteSeq<T>`, an interface parameter |
| `new slice<byte>(s)` — Go's `[]byte(s)` | one box per call | the constructor took `IByteSeq<T>` |
| `new @string(s)` — Go's `string(s)` | one box per call | the constructor took `IByteSeq<byte>` |

A `parseRFC3339`-shaped body (seven sub-slices, eight `len` calls, six `[]byte(s)` conversions per parse) measured **720 B/parse** on the `slice<byte>` instantiation and **776 B/parse** on `@string` — for Go code that allocates nothing. The remedy is one idea applied four times: *never name the sequence type as an interface in a signature a generic body reaches*.

- **Sub-slicing** moves to the self-referential `TSelf this[Range]`. Both implementers' public range indexers already return their own type, so this is satisfied implicitly, and the call becomes a `constrained.` direct dispatch on the value type. The converter's `((bytes)(…))` cast survives as a no-op identity conversion.
- **`len`** takes the constrained type parameter (`len<TSeq>(TSeq) where TSeq : IByteSeq`) instead of the interface.
- **`[]byte(s)` / `string(s)`** become the `ToSlice`/`ToGoString` **extension methods** rather than constructors. C# has no generic constructor, so a constructor can only accept the interface; and a static factory cannot even be *named* here, because converted code carries `using static go.builtin`, which shadows the `slice` and `@string` type names with the builtin conversion methods (`slice<byte>.From(s)` is CS0119 — "is a method, which is not valid in the given context"). An extension call is member access on the receiver, so it sidesteps both problems. Each folds `typeof(TSeq) == typeof(…)` to a per-instantiation constant, so the sharing case reduces to a field copy.

Measured after: **0 B/parse** on `slice<byte>`, and 776 → 416 B/parse on `@string` (what remains there is the `byte[]` each `[]byte(string)` conversion must materialize — Go copies too). Guarded by GolibTests `ByteSeqAllocationTests`, which asserts the measured bytes and carries a deliberately-boxed control that must still report the old 720 B/parse. That control pins its box behind a `[MethodImpl(NoInlining)]` boundary on purpose: the JIT elides a box/unbox pair written adjacently in one method, so an inline control would report 384 B/parse and understate what the redesign is worth. The real interface indexer boxed inside a callee and returned, which is why the cost was really paid.

### Explicit type arguments come from the callee's instantiation
A generic function's explicit type arguments are read from the CALLEE's resolved instantiation (`info.Instances`), not from the RESULT type's arguments -- the two lists differ whenever the callee has more type parameters than the result names. reflect's `rangeNum[T, N](num N) iter.Seq[T]` called `rangeNum[int8](v)`: the result `Seq[T]` carries ONE argument where the method needs TWO (CS0305). The result's own arguments still gate *whether* to emit, so a generic callee returning a plain named type keeps C# inference:
```csharp
return rangeNum<int8, int64>(v.Int());
```
Guarded by `GenericTypeInference` (`seqOf[T ~int64, N ~int32 | ~int64](n N) Seq[T]`).

### Increment/decrement on a type parameter
`i++` / `i--` on a constrained type parameter binds `IIncrementOperators<T>` / `IDecrementOperators<T>`, which the lifted **Arithmetic** operator set now includes (reflect `rangeNum`'s loop, CS0023). They live in the numeric-only Arithmetic set -- never the string-including Sum set, since `@string` implements neither. The list is emitted in two places that must stay in sync: the converter's `getLiftedConstraints` (`constraintOperations.go`) and the go2cs-gen `InterfaceTypeTemplate`.

### Unary negation on a type parameter
`-x` on a constrained type parameter binds `IUnaryNegationOperators<T, T>`, which the lifted
**Arithmetic** operator set now includes (math/rand/v2's
`func keep[T int | uint | int32 | uint32 | int64 | uint64](x T) T { return -x }`, CS0023). Like
increment/decrement it is numeric-only — `@string` has no negation — so it never joins the Sum set.

Satisfying it needed a matching change on the generator side, because a NAMED Go numeric type is
instantiated through its go2cs-gen wrapper. Go defines `-x` on EVERY numeric type, unsigned
included, as the wrap-around `0 - x`; C# has no unary minus for `ulong` at all and widens
`uint`/`ushort`/`byte` to a signed type, which is why `NumericTypeTemplate` previously emitted the
operator only for signed types. It now emits the unsigned form as that subtraction under
`unchecked` — exactly Go's semantics — so a generic over `~uint64` instantiated with a named
unsigned type (internal/trace's `dataTable[EI ~uint64, E]` over `type stringID uint64`) satisfies
the constraint instead of failing CS0315:

```csharp
// generated for `type counter uint64`
public static counter operator -(counter value) => (counter)unchecked((uint64)((uint64)0 - value.m_value));
```

The list is emitted in THREE places that must stay in sync: the converter's `getLiftedConstraints`
(`constraintOperations.go`), the go2cs-gen `InterfaceTypeTemplate` "Arithmetic" constraint list, and
`InheritedTypeTemplate`'s `NumericInterfaces` declaration list (whose operator bodies come from
`NumericTypeTemplate`). Guarded by `GenericNegation`, which negates across the primitive widths and
through named types over both a signed (`~int32`) and an unsigned (`~uint64`) underlying type,
output-compared against Go so the unsigned wrap is verified rather than merely compiled.

### `uintptr` as a generic numeric type argument
The golib `uintptr` struct declares the full generic-math interface set the lifted numeric constraints demand (`IAdditionOperators` through `IComparisonOperators`, `IShiftOperators<uintptr, int, uintptr>` with a `>>>` operator, `IIncrementOperators`/`IDecrementOperators`) -- matching operators alone never satisfy a C# where-clause (CS0315 at reflect's `rangeNum<uintptr, uint64>`). At runtime, `ConvertToType`/`ConvertToUInt64` have `uintptr` fast paths, and the reflection-cached `TypeParamCaster` probes a public `Value` FIELD as well as the generated wrappers' `Value` property (hand-written wrappers keep a field for `Interlocked`/`Volatile` `ref x.Value` seams). Guarded by `GenericTypeInference` (`growShrink[U ~uint32 | ~uintptr]`).

> **Latent gap ([banked](Glossary.md#banked)):** generated `[GoType("num:*")]` wrapper structs do NOT yet declare the generic-math interfaces -- a NAMED numeric wrapper used as a union-generic type argument would CS0315. No corpus site hits this yet.

### Union-constrained sub-slices cast back to the type parameter
A sub-slice of a `string | []byte` union-constrained value is typed by Go as the type parameter again, so it assigns back to, passes as, and returns as the parameter (time format_rfc3339, CS0266/CS0310/CS0029 before the cast landed). The emission wraps the range forms in an explicit conversion to the type parameter:
```csharp
return parse(((T)(s[0..2]))) + parse(((T)(s[3..5])));
```
Func-literal parameters typed as the union type parameter render as the parameter itself (the enclosing method's type parameter is in scope inside a lambda), matching the Go:
```csharp
var parse = (T part) => {
```
Since the constraint became self-referential (`IByteSeq<T, byte>`, above), the Range indexer returns `T` directly, so this cast is an **identity conversion that emits no IL** — it was a runtime-checked unbox of a boxed struct when the indexer returned the interface. The emission is kept because it names the type Go gives the expression; it no longer costs anything.

Guarded by `StringByteUnionConstraint` (`trimHead`/`headSum`; `digitSum`).

### Spreading a union-constrained value
A union-constrained value may also be **spread** into a variadic — encoding/json's `appendString[Bytes []byte | string]` does `append(dst, src[lo:hi]...)` (and the open-ended `append(dst, src[lo:]...)`). The sub-slice is typed as the type parameter again, so the cast-back above wraps it, and the spread renders as `((Bytes)(src[lo..hi])).ꓸꓸꓸ`. A bare type-parameter value has no members of its own, so the spread `ꓸꓸꓸ` (which yields the `Span<byte>` the `append<T>(slice<T>, params Span<T>)` overload binds) must be declared on the **constraint interface** — a member access on a constrained type-parameter value resolves through its constraint. `IByteSeq<T>` therefore exposes `Span<T> ꓸꓸꓸ { get; }`; both implementers already satisfy it (`slice<T>` as `Span<T>`, `@string` as `Span<byte>`), so the interface member is implicit and adds no cast (CS1061 otherwise — the type parameter `Bytes` had no `ꓸꓸꓸ`). (Guarded by the `StringByteUnionConstraint` extension `appendRun` — a bounded and an open-ended sub-slice of the union value spread into `append`, both instantiations value-compared vs Go.)

## Type Aliasing
Go supports two kinds of [type aliasing](https://go101.org/article/type-system-overview.html#type-definition): a "type definition" and a "type alias declaration".

### Type Definitions
For a Go "type definition" the new type is a distinct type that shares an [underlying type](https://go101.org/article/type-system-overview.html#underlying-type) with its base. Because converted types are structs (no inheritance), the converter relies on the source generators (see [Source Generators](#source-generators)) to emit the bridging needed for these to be used interchangeably while remaining distinct: implicit conversion operators down to the underlying type (via `ImplicitConvGenerator` / `TypeGenerator`), and, where the base is a built-in like `slice`, the relevant interface (`ISlice<T>`, etc.) implementation. A named type also supports the extension methods (receiver functions) of its underlying types, which the generators surface as proxy/overload methods.

When a pointer conversion `(*Target)(srcPtr)` bridges two structurally-identical structs, the converter records an **indirect** (boxing) implicit conversion `Source → ж<Target>` and `ImplicitConvGenerator` emits `implicit operator ж<Target>(Source src) => Ꮡ(new Target(<members>))`. For a **self-boxing** conversion — `Source` and `Target` are the *same* struct (`mspan → ж<mspan>`), which arises from a self-referential struct's recursive sub-struct conversions — that member-by-member reconstruction is both unnecessary and wrong: a pointer field whose target ctor parameter is itself a `ж<…>` was deref'd (`src.f?.Value ?? default!`), and a value cannot bind a pointer parameter (CS1503). The generator detects self-boxing (the boxed element type equals the source) and emits `Ꮡ(src)` instead — boxing a copy of the whole struct directly, identical in effect for a pointer-free struct and correct for one with pointer fields. (Validated by the green baseline build, which regenerates every `.g.cs`, plus the `TypeConversion` behavioral test for the non-self-boxing form; runtime exercised self-boxing for `mspan`, `g`, `stackScanState`, `hmap`, etc.)

### Type Alias Declarations
For a Go "type alias declaration" the alias matches C# aliasing implemented with the `using` keyword. Since the alias may be exported and referenced across files, the converter emits a **global** `using` (C# 10's [Global Using Directive](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/GlobalUsingDirective.md)) into the package's generated aliases. For example:

```go
type P = *bool
type M = map[int]int
type table = map[string]int
```
```csharp
global using P = go.ж<bool>;
global using M = go.map<nint, nint>;
global using table = go.map<@string, nint>;
```

**A `global using` RHS renders csproj-alias numerics as C# keywords.** C# resolves a using directive's target *without reference to other using directives* — aliases are invisible to one another — so the golib csproj-level aliases (`uint64`, `float64`, `any`, …) that resolve everywhere else in the compilation are CS0246 inside `global using X = …;`. The alias-declaration emission (only) rewrites those names to their using-safe keyword/BCL equivalents: fiat's `type p224UntypedFieldElement = [4]uint64` emits `global using p224UntypedFieldElement = go.array<ulong>;`. Body code keeps the Go-visual alias names; already-safe names (`byte`, `bool`, `nint`, `go.@string`) are untouched, and the rewrite deliberately skips dot-qualified names so a package type sharing a builtin name is left alone. (Guarded by the `AliasStructComposite` extension `words` — an alias to `[4]uint64` used as a parameter type, output-compared.)

**An alias to an unnamed array/slice resolves through `types.Unalias` at type-switched decision points.** An alias is a `*types.Alias` (Go 1.22+) — neither the AST's `ast.ArrayType` nor the resolved `*types.Array` — so an emission that type-switches on the syntax node or the unresolved type misses it. Three sites resolve through `types.Unalias`: (1) the **`range` operand dispatch** — `for _, e := range w` on an alias-typed array previously matched no arm and emitted the *whole loop* as a C# comment, a silent behavioral hole; it now emits the normal `foreach (var (_, e) in w)`. (2) the **composite-literal dispatch** — `words{10, 20, 30, 40}` emits the same element-array projection the unnamed literal uses, `new uint64[]{10, 20, 30, 40}.array()`; the alias name renders as an Ident (not an `ast.ArrayType`), so it cannot take the composite-initializer bracket rewrite, and keeping it produced a C# collection initializer on the alias (`new words{…}` — CS1061, `array<T>` has no `Add`). (3) **`var` declarations** — a local or package-level `var w words` allocates the fixed-size backing (`words z = new(4);` / `internal static words gw = new(4);`) instead of `default!`/uninitialized, whose null backing array throws NRE on the first element write. An alias to a *named* type is unaffected: unaliasing lands on `*types.Named` and the existing wrapper-struct arms apply, with the alias name preserved. (Guarded by the `AliasStructComposite` extensions — alias-typed range loop, composite literal, and local + global `var` with element writes, values vs Go.)

**A same-package alias TARGET carries the package's FULL namespace, not just its class.** An exported alias whose target is *lifted* into the package class — `type CorpusEntry = struct{…}` lifts its anonymous struct to a nested `CorpusEntryᴛ1` (and the same for an alias to a same-package named type) — must qualify that target with the package's whole namespace. For a package in a **nested** namespace (`internal/fuzz` → namespace `go.@internal`, class `fuzz_package`; `net/http` → `go.net` / `http_package`) the lifted type lives at `go.@internal.fuzz_package.CorpusEntryᴛ1`. Building the qualifier from the bare `<pkg>_package` class alone dropped the intervening namespace segment, so the emitted `global using CorpusEntry = go.fuzz_package.CorpusEntryᴛ1;` — and the matching `[assembly: GoTypeAlias("CorpusEntry", "go.fuzz_package.CorpusEntryᴛ1")]` that every consumer replays verbatim through its `<ImportedTypeAliases>` block — named a namespace that does not exist → CS0234 at the using-alias line and at every use (internal/fuzz's `CorpusEntry`, ×60). The qualifier is now taken from the same `packageNamespace` that emits the `namespace …;` declaration (minus the root, plus the class), so the target and the declaration always agree: `go.@internal.fuzz_package.CorpusEntryᴛ1`. A **top-level** package's namespace is exactly the root (`go`), leaving no intervening segment, so its target stays `go.<pkg>_package.…` — the emission is byte-for-byte unchanged there. (Guarded by the `NestedAliasUser` behavioral test — a top-level `package main` that imports its own nested `inner` subpackage, whose C# namespace is `go.NestedAliasUser`; `inner` exports an anon-struct alias `Entry`, and both `inner`'s own `global using` and the consumer's imported `global using innerꓸEntry` resolve to `go.NestedAliasUser.inner_package.Entryᴛ1`, values vs Go.)

## Delegates to Value Receiver Instances

**A Go METHOD EXPRESSION** — `(*timers).run`, the *unbound* method as a func value whose first parameter is the receiver (runtime `time.go`'s `abi.FuncPCABIInternal((*timers).run)`) — selects a method off a **type**. Emitting the selector naively renders the type in value position (`(ж<timers>).run` — CS0119 + CS1503). Go types the expression as the func signature with the receiver prepended, so the converter renders that signature as the concrete delegate type and casts the method's static form to it: `(Func<ж<timers>, int64, int64>)(run)`. For a `[GoRecv]` method the `RecvGenerator`'s ж-overload matches the delegate exactly; a value-receiver method expression (`counter.get`) casts to its value-typed delegate (`(Func<counter, nint>)(get)`); a direct-ж method's primary form matches directly. (Guarded by the `MethodExpression` behavioral test — pointer- and value-receiver method expressions assigned, passed inline, and *invoked*, with mutations accumulating through the receiver box, values vs Go.)

A method expression on a **FOREIGN type** — `(*http.Request).Write` (net/http/httputil persist.go), or `(*Reader).ReadBytes` in an EXTERNAL test (`package bufio_test`) that dot-imports `bufio` — must additionally **qualify** the method's static form: the `[GoRecv]`/extension static (and its `RecvGenerator` ж-overload) lives in the *defining* package's class, so the bare name is CS0103 (a `using static` imports an extension method only for `recv.M()` invocation, never as a bare method group — so `(Func<…>)(ReadBytes)` cannot bind it; a same-named local method would instead mis-bind, CS0123). The qualifier is derived from the method's OWN package via `go/types` (`importQualifier(obj.Pkg().Name())`, with the file's import-alias override) — identical to how `getAliasQualifiedTypeName` qualifies the receiver type inside the delegate (`bufio.Reader`) — rather than by peeling the Go source spelling: a **dot-imported** type is a BARE ident (`Reader`), not a `pkg.T` selector, so the old source-peel silently dropped the qualifier and emitted the bare name. The result is `(Func<ж<http.Request>, io.Writer, error>)(http.Write)` / `(Func<ж<bufio.Reader>, byte, (slice<byte>, error)>)(bufio.ReadBytes)`. A same-package method expression keeps the bare name — the static is in scope — so existing emissions are unchanged. (Guarded by the `CrossPkgUser` extension — pointer-receiver `(*CrossPkgLib.Sensor).Calibrate` (write observed through the original receiver) plus value-receiver `Sensor.Hot` / `Celsius.Add` foreign method expressions, each invoked through its func value, output-compared vs Go — and by the `MethodExprDotImport` behavioral test, which dot-imports a sibling package and uses `(*Reader).Read` / `(*Reader).Peek` as func values; the pre-fix converter fails it with bare `(Read)`/`(Peek)` — CS0103 — exactly the bufio `TestUnreadByteOthers` failure.)

The **bound method value** — `d.compute = metricReader(read).compute` (runtime `metrics.go`), `types.MethodVal` used as a *value* — forwards through a lambda that captures the receiver expression and carries the **method's own parameters**, explicitly typed: `(ж<statAggregate> p1, ж<metricValue> p2) => ((metricReader)read).compute(p1, p2)`. The previous emission hardcoded arity zero (`() => x.m()`), mismatching any non-nullary target delegate (CS1593). One documented divergence: the receiver expression is evaluated *inside* the lambda (per call), where Go binds it once at method-value creation — acceptable for the compile milestone and the simple receivers observed. (Guarded by the `MethodExpression` extension — a bound `c.add` invoked repeatedly, mutations accumulating through the bound receiver, values vs Go.)

An **INTERFACE-receiver** method value in assignment context is exempt from that lambda: an interface method is a genuine C# instance method, so a plain method **group** over the evaluated receiver expression both compiles and matches Go's bind-once semantics exactly — `f = conf.Sizes.Alignof` (go/types sizes.go, `conf` the `ref` receiver) emits `f = conf.Sizes.Alignof;`, evaluating `conf.Sizes` once at delegate creation. The synthesized lambda there was doubly wrong: it re-evaluated the receiver per call *and* captured `conf` — capturing a `ref` receiver is CS1628. This mirrors the value-context rule below, which already leaves interface receivers on the plain emission; whole-stdlib footprint of the change: sizes.cs ×6, database/sql convert.cs, debug/buildinfo, net/http h2_bundle — every hunk a lambda collapsing to its method group. (Guarded by the `IfaceFieldMethodValueBind` behavioral test — a method value on an interface field of a pointer receiver with the field REBOUND after the value is taken, proving bind-once, output-compared vs Go.)

A **POINTER-receiver method value in a value context** — passed as a call argument rather than assigned: `s.nonDefaultOnce.Do(s.register)`, `registerMetric(…, s.nonDefault.Load)` (internal/godebug) — cannot use the bare selector: the `[GoRecv]` emission is an extension method whose first parameter is a **value type**, and C# cannot create a delegate from that (CS1113/CS1061). Go binds the receiver **address** once at method-value creation (`s.register` ≡ `(&s).register`), so the converter emits exactly that binding as a **box-bound method group** over the `RecvGenerator`'s ж-overload (class-typed, delegate-legal): `Ꮡs.register` for the receiver itself, `Ꮡs.of(Setting.ᏑnonDefault).Load` for a receiver value-field chain (the `&x.field` machinery renders the real field box). Unlike the assignment-context lambda above, this form matches Go's bind-once semantics exactly. A method whose body contains such a method value on its own receiver (or a value-field chain of it) is promoted to **direct-ж** by the capture-mode pre-pass (`bodyHasPointerMethodValueOnReceiver`) so the receiver box `Ꮡrecv` exists in scope. (Guarded by the `ReceiverFieldMethodCall` extension — method values on the receiver, on a receiver value field, and on a boxed local's field, passed as func values and invoked with mutations landing on the real storage, values vs Go.)

The **VALUE-receiver** analog captures rather than binds. When a value-receiver method value roots at the enclosing method's receiver — `kdf.hash.New` (crypto/internal/hpke's `hkdfKDF`, whose `hash` field is a `crypto.Hash` and `New` a value-receiver method; also crypto/tls's `c.hash.New`) — the emitted method is an extension over a **value** receiver, which likewise has no C# delegate (CS1113), so the converter synthesizes a wrapping lambda carrying the method's own parameters: `() => kdf.hash.New()`. But that lambda **captures the receiver**, and a non-direct-ж pointer-receiver method renders `this ref hkdfKDF kdf` whose `ref var kdf = ref Ꮡkdf.Value` alias cannot be captured by a C# closure (**CS1628** — "cannot use ref/in/out parameter inside a lambda"). So a method whose body contains such a method value is promoted to **direct-ж** by the capture-mode pre-pass (`bodyCapturesReceiverInValueMethodValue`, the value-receiver sibling of `bodyHasPointerMethodValueOnReceiver`), giving it a receiver box `Ꮡkdf` that the synthesized lambda references as a capturable reference: `() => Ꮡkdf.Value.hash.New()`. Two supporting pieces make the receiver render through its box inside the *synthesized* lambda (which has no `*ast.FuncLit` node): the capture-analysis walk now marks the **field-chain root** receiver box-ref, not only a bare-ident receiver (`kdf.hash.New` roots at `kdf`, not a bare ident); and the value-receiver synthesis renders the receiver expression in a lambda-conversion context (`conversionInLambda`) so `convIdent` emits the `Ꮡkdf.Value` box form. Same documented divergence as the other method-value forms — the receiver expression re-evaluates *inside* the lambda (per call), where Go's value-receiver method value binds a copy of it once at creation; acceptable for the compile milestone (the closure sees the same receiver instance, matching the pointer-receiver semantics of the enclosing method). This also cleared the identical latent CS1628 in crypto/tls's `key_schedule.cs` (`expandLabel`/`extract`/`finishedHash` each pass `c.hash.New` to `hkdf`/`hmac`, and the direct-ж fixpoint promoted their callers `deriveSecret`/`trafficKey`/… with call sites adapting to `Ꮡc.expandLabel` / `Ꮡsuite.trafficKey`). (Guarded by the `ReceiverCapturedInClosure` extension — a pointer-receiver method capturing its receiver through a value-receiver method value on a value field-chain (`w.id.render`) and on the bare receiver (`w.tag`), alongside the pre-existing func-literal capture, all invoked and output-compared vs Go; whole-stdlib reconvert diff: exactly hpke + the two crypto/tls files changed, nothing else.)

The **go-statement sibling** of the receiver-capture family: a `go` statement calling a **value-returning** method through the enclosing method's pointer receiver — `go q.conn.HandshakeContext(ctx)` inside `func (q *QUICConn) Start` (crypto/tls quic.go, CS1628) — is FORCED into the synthesized-lambda emission because `goǃ` has only void `Action` overloads (the x/net/nettest CS0407 form): `goǃ(ᴛ1 => q.conn.HandshakeContext(ᴛ1), ctx)`. That lambda references the receiver exactly like the method-value cases above, but neither closure predicate sees it — there is no `*ast.FuncLit` and no method-VALUE expression, only a go-call whose lowering *will* synthesize one. The capture-mode pre-pass therefore also promotes on `bodyHasGoStmtLambdaCapturingReceiver`, which mirrors `visitGoStmt`'s lambda-form decision (a nullary call synthesizes a lambda only for a value-returning or named-func-type callee; a call with arguments does so when the callee returns a value or the arity mismatches — variadic never matches) and fires when the CALLEE expression references the receiver (arguments render outside the lambda, as `goǃ` call arguments). With the method direct-ж, the go-stmt capture analysis' existing box-ref marking of the receiver (`varIsDerefdPointerParam`) takes effect and the lambda renders the chain through the box: `goǃ(ᴛ1 => Ꮡq.Value.conn.HandshakeContext(ᴛ1), ctx)`. The method-group emissions are excluded and unchanged — a void matching-arity callee (os/exec's `go c.watchCtx(resultc)`) binds the receiver chain at delegate-creation time, outside any lambda; a `defer` sibling needs no equivalent because any function-level defer already promotes via `bodyWrappedInDeferContext`. **Known divergence (Phase-4 item):** the synthesized lambda reads the receiver chain (`Ꮡq.Value.conn`) at **goroutine-run time**, whereas Go evaluates the method-value receiver at **go-statement time** — `go q.conn.M(x); q.conn = other` deterministically calls the OLD conn in Go but races toward the NEW one here (arguments are statement-time in both). The same lazy-chain window already exists for every synthesized go-lambda over a non-receiver chain (the CS0407 discard form); the faithful fix for the whole class is hoisting the receiver-chain prefix into a statement-time temp before the lambda, which would also avoid the direct-ж signature flip — direct-ж was chosen here for machinery reuse under the compile-first milestone. (Guarded by `GoStmtReceiverLambda` — `go e.tally.bump(delta)` (argument arm) and `go e.tally.report()` (nullary arm), both value-returning through a pointer field of the receiver, with the goroutine's writes read back through the original receiver chain, output-compared vs Go.)

A conversion to a **named func type** — `metricReader(read)` where `type metricReader func() uint64` — targets a C# **delegate declaration** (`internal delegate uint64 metricReader();`). Distinct delegate types have no cast conversion (a `(metricReader)read` from `Func<ulong>` is CS0030); C# converts via **delegate creation**: `new metricReader(read)`, which accepts a compatible delegate or method group. The general conversion branch special-cases a named target whose underlying is a `*types.Signature`. Composed with the bound-value lambda this renders the full runtime `metrics.go` registration: `d.compute = (ж<statAggregate> p1, ж<metricValue> p2) => new metricReader(read).compute(p1, p2)`. (Guarded by the `MethodExpression` extension — a named-func-type conversion with a bound method invoked through a func field, values vs Go.)

A **GENERIC defined function type** — Go 1.23 `iter`'s `type Seq2[K, V any] func(yield func(K, V) bool)` — emits a **generic delegate**: `public delegate void Seq2<K, V>(Func<K, V, bool> yield);`. Two converter details make this work: the type parameters live on the NAMED type, not the `*ast.FuncType`'s signature, so the delegate declaration derives its generic definition from the TypeSpec's defined type (as the struct/array paths do — deriving from the signature emitted a non-generic `Seq2` whose `K`/`V` were undefined, CS0246/CS0308); and a **conversion to a generic instantiation** (`Seq2Like[string, int](fn)`) peels `IndexListExpr` (multi-parameter — the single-parameter `IndexExpr` already peeled) and resolves the *instantiated* target from the Fun expression's type (the TypeName resolves to the uninstantiated generic, against which convertibility fails), then routes through the same delegate-creation form: `new Seq2Like<@string, nint>((@string k, nint v) => …)`. The instantiated-target override is gated to uninstantiated-generic named targets so pointer conversions (`(*uint64)(p)`, whose Fun type is the full `*T` with the `*` re-applied separately) are untouched. (Guarded by the `GenericTypeInstantiation` extension — a generic defined func type declared, instantiated with two type arguments, and invoked both through a generic function and directly, values vs Go; clears the `iter` package's five wave-1 errors.)

In Go a function is a value; a value-receiver method can be assigned to a variable, and the variable captures **its own copy** of the receiver value at the moment of assignment. This surprises many non-Go programmers:

```go
package main

import "fmt"

type data struct {
    name string
}

func (d data) printName() {
    fmt.Println("Name =", d.name)
}

func main() {
    d := data{name: "James"}
    f1 := d.printName
    f1()
    d.name = "Gretchen"
    f1()
}
```
This prints `Name = James` twice ([run it](https://play.golang.org/p/d-A5re1dfs8)) — `f1` bound a copy of `d`, so the later mutation is not observed. To preserve this semantic, the converter copies the receiver value into the delegate's capture rather than capturing the variable by reference, so the delegate executes against the snapshot taken at assignment time.

### A method value reassigned via `=` hoists its receiver capture
The receiver-snapshot decl above is emitted as a full statement (`var dʗ1 = d;`) before the lambda. In a `:=` **declaration** this hoists naturally, but a plain `=` **reassignment** to a pre-declared variable — database/sql's `checker = nvc.CheckNamedValue` — already wrote the LHS and `=` operator by the time the snapshot is generated, so writing it inline split the assignment into three token-broken pieces (CS1002). The converter routes the snapshot to the statement hoist buffer so it precedes the whole statement:

```go
var checker func(*driver.NamedValue) error
checker = nvc.CheckNamedValue   // reassign a method value
```
```csharp
var nvcʗ1 = nvc;
checker = nvcʗ1.CheckNamedValue;
```

This matches the `:=`-define path (which already hoists) and also covers a reassignment inside a tagless `switch` case. (Guarded by the `MethodValueReassignCapture` behavioral test.) `CheckNamedValue` here is an *interface* method, so the assignment binds a plain method group over the hoisted snapshot (see the interface-receiver rule above); a concrete-receiver method value keeps the param-carrying lambda form, referencing the snapshot the same way.

### A POINTER-receiver method value binds the ADDRESS in assignment context too
Go's `sw.Closesocket`, where `Closesocket` has a `*Switch` receiver and `sw` is addressable, **is**
`(&sw).Closesocket` — the address is taken once, at method-value creation (the same spec rule the
heap-box arm above rests on). The VALUE-context arm — a method value passed as a call *argument* —
already synthesized that `&` and bound the method group over the box. The **assignment**-context arm
did not: it rendered the receiver expression plainly and forwarded through the param-carrying lambda,
so the lambda body called the `[GoRecv]` `ж<T>` extension with a struct **value** receiver. net's six
socket-hook installs in `main_windows_test.go` were five CS1929 plus one CS1501, the latter because
the value receiver made a *different*, differently-arity'd overload the compiler's best candidate:

```go
poll.CloseFunc = sw.Closesocket      // sw is a package-level socktest.Switch
listenFunc     = sw.Listen
```
```csharp
poll.CloseFunc = Ꮡsw.Closesocket;    // was (syscallꓸHandle p1) => sw.Closesocket(p1)
listenFunc     = Ꮡsw.Listen;
```

The arm takes the value-context emission **wholesale** — a method GROUP over the box — rather than
only re-pointing the forwarding lambda's receiver, because two things were wrong, not one:

- The **lambda** is unnecessary here. A pointer-receiver method value binds once and aliases, so the
  group is both simpler and strictly more faithful than the lambda's documented per-call receiver
  re-evaluation. (The lambda remains right for a *value* receiver, where C# cannot build a delegate
  over a value-type extension at all.)
- The receiver **snapshot** was the deeper error, and only a local receiver exposes it. The snapshot
  exists to preserve a value receiver's bind-a-COPY semantics, which a pointer receiver does not
  have — the escape analysis has already ruled the other way by heap-boxing the local (the rule
  *A pointer-receiver METHOD VALUE heap-boxes its receiver* above) — and it *renames* the receiver, so
  the synthesized `&` produced `Ꮡcʗ1`, a box nothing declares (**CS0103**). `visitAssignStmt` now
  skips the snapshot for this shape at both of its method-value sites
  (`methodValueBindsReceiverAddress`).

net's own sites never showed the second half: `sw` is package-level, so no capture snapshot is taken.
The local-receiver form was broken before this change too — as CS1929 rather than CS0103 — so the
guard moved the error rather than introducing it. A receiver expression that is *already* a pointer
keeps both its existing pointer-context rendering and its snapshot: Go copies the pointer there, which
is exactly what the snapshot models. (Guarded by `MethodValueReassignCapture`'s `counter.bump` arm — a
pointer-receiver method value assigned to a pre-declared func var, with the mutation read back through
the original local, output-compared vs Go.)

### A bare function value in `:=` takes its named delegate type, not `var`
Go's short-declaration from a bare function value whose type is a **named** func type — text/template/parse's `state := lexText`, where `lexText` is `func(*lexer) stateFn` and `type stateFn func(*lexer) stateFn` (the classic self-referential state machine) — infers the local as the *unnamed* signature. The converter cannot emit `var state = lexText;` (a C# method group has no `var`-inferable delegate type — CS8917), and typing the local structurally as `Func<ж<lexer>, stateFn>` makes it a **distinct** C# delegate from the `stateFn` the method group produces and that each `state = state(l)` reassignment yields (CS0029). It declares the local with the matching package named delegate instead:

```go
state := lexText
for state != nil {
    state = state(l)
}
```
```csharp
stateFn state = lexText;
while (state != default!) {
    state = state(l);
}
```

A `:=` from a method group whose signature matches no package named func type keeps the existing path. (Guarded by the `NamedFuncTypeStateMachine` behavioral test.)

## Defer / Panic / Recover

### A function that defers or recovers emits its body INLINE, inside a frame

A Go function is a stack frame: it registers `defer` records in its own frame and runs them on the
way out, whatever the way out is. The converted C# says the same thing with statements — the body is
emitted **inline** in the method, wrapped in `try`/`catch`/`finally`, beside a `GoFrame` local that
holds this call's defer list and nothing else:

```csharp
internal static void Main() {
    GoFrame ᒐ = default;
    try {
        fmt.Println(openFileˢ);
        defer(ᴛ1 => fmt.Println(ᴛ1), closeFileˢ, ref ᒐ);
        fmt.Println(writeDataToFileˢ);
    }
    catch (Exception ᒐex) when (GoFrame.IsPanic(ᒐex, out PanicException? ᒐp)) { GoFrame.Capture(ᒐp); }
    finally { ᒐ.Run(); }
}
```

Each part does one of the three things Go's runtime does for free:

- the **`catch`** parks a panic — an explicit `panic()` or a .NET exception that maps to a Go runtime
  panic — where `recover()` can read it. The filter is the single adoption point that also snapshots
  the panic's origin, so a non-panic exception (and `GoexitException`, deliberately) fails it and
  propagates unchanged;
- the **`finally`** drains the deferred calls, which is Go's guarantee that they run on *every* exit
  path — normal return, panic, `runtime.Goexit`, or a mapped runtime fault. They run **after** the
  panic has been parked, which is exactly what lets a deferred call recover the panic raised by the
  body it was registered in;
- the **frame** is the defer list. It is a `ref struct`, so it lives in the method's own stack frame,
  the JIT can enregister its four inline slots, and the machinery allocates **nothing**.

`GoFrame.Run()` is the whole of the ordering contract: LIFO drain, the `HandledPanic` save/restore
that keeps a traceback honest for the length of the deferred sequence, the re-panic origin
inheritance behind Go's `defer func(){ panic(recover()) }()` idiom, and the final re-throw of a panic
no deferred call recovered.

Guards: `DeferSimple`, `DeferCallOrder`, `DeferClosure`, `PanicRecover`, `GoexitDefers`,
`DeferFrameScopes`, and golib's `GoFrameTests` (which A/B every scenario against the machinery this
replaced).

### Why the body is not a lambda

The obvious alternative models the same three things as an *object* that owns the body — a
`func((defer, recover) => …)` execution context supplying a catch, a finally and a `Stack<Action>`.
Owning the body forces the body to be a delegate; a delegate forces a display class for everything
the body touches; and a display class forces a generic ladder for everything a delegate *cannot*
capture (a `ref` local, a `Span`). The frame form needs none of it, and avoids two things beyond the
machinery:

- **a capture-semantics divergence class, by construction.** A body-owning lambda closes over
  variables the Go original never closed over; an inline body closes over nothing at all, and only
  the deferred closures capture — exactly as Go's deferred closures do.
- **the ref-parameter ladder.** A variadic deferring function would have to thread its `params Span`
  through the wrapper, because a lambda cannot capture one. An inline body has no parameters to
  thread, so it simply uses the one it has (`GenericVariadicFunc`, `VariadicPointerParam`).

Measured with `GC.GetAllocatedBytesForCurrentThread` over 5,000 Release calls, the frame costs
**0 B** with no defers, **0 B** with one or two whose targets are cached static method groups, and
**192 B** for the two-capturing-defer shape of `internal/poll.FD.Write` — the residue being the
display class and delegate of each defer that genuinely closes over something. The body-owning
alternative measures **160 B**, **248 B** and **440 B** for the same three. The full as-built record
of the frame's design lives in [`phase4/DESIGN-closure-emission.md`](phase4/DESIGN-closure-emission.md).

### The named-result form: results outside the try, exits through a label

`func f() (r int) { defer func(){ r++ }(); return 1 }` returns **2** in Go: the deferred call runs
after the result parameter is assigned and before the caller sees it. A C# `finally` cannot change a
value the `return` has already evaluated, so the results are declared **before** the `try` and read
back **after** the `finally`, and every exit inside the `try` leaves through a `goto` — which runs the
`finally` exactly as a `return` would, without freezing a result the deferred calls may still change:

```csharp
internal static (nint @out, @string label) compute(nint x) {
    nint @out = default!;
    @string label = default!;
    GoFrame ᒐ = default;
    try {
        defer(() => {
            @out += 1000;
        }, ref ᒐ);
        if (x < 0) {
            (@out, label) = (-1, negˢ);
            goto ᒐdone;
        }
        (@out, label) = (@double(x), fmt.Sprintf("v=%d"u8, x));
    }
    catch (Exception ᒐex) when (GoFrame.IsPanic(ᒐex, out PanicException? ᒐp)) { GoFrame.Capture(ᒐp); }
    finally { ᒐ.Run(); }
    ᒐdone: return (@out, label);
}
```

The label is emitted only when something jumps to it — a body that simply falls off the end of the
`try` reaches the return anyway. A heap-box-backed named result keeps the split it already had: the
box is declared outside the `try` and the value alias re-derived inside it, because the deferred
closures are still lambdas and a lambda cannot capture a `ref` local.

Guards: `NamedReturnDefer`, `NamedResultDeferCapture`, `DeferFrameScopes` (which exits from inside a
loop, a switch arm and an `if`).

### The catch arm returns Go's zero results

For a value-returning function whose results are unnamed, the catch arm ends with `return default!;`.
A panic a deferred call **recovered** leaves the function returning the zero results, which is Go's
rule; one **no** deferred call recovered never reaches that return at all, because `Run()` re-throws
it from the `finally` and a throw from a `finally` overrides a pending return. It is also what keeps
the method's endpoint unreachable, so nothing is needed after the `try` statement.

### `recover()` is a static call, not a parameter

`recover()` resolves to `builtin.recover()`, which reads the one thread-local slot the emitted
`catch` parked the panic in (`GoFrame.Capture`). That it resolves **statically** is not an
optimization — it is the load-bearing fact of the whole design: a deferred closure can therefore
recover without holding any handle on the frame that registered it, which is what allows the frame to
be a `ref struct` in the first place.

One consequence: `recover` is a Go *predeclared identifier*, not a keyword, so a package may declare
its own — `text/template/parse` has `func (t *Tree) recover(errp *error)`. Inside that package's
class the extension method wins over the `using static go.builtin` import, so such a package emits
its built-in calls qualified as `builtin.recover()`, exactly like every other shadowed built-in.

### `defer` registration: the arity ladder, and no bang

Go evaluates a deferred call's ARGUMENTS at the `defer` statement and runs the call later, so every
argument is captured at registration. `defer` is an arity ladder of generic rungs (`Action` and a
result-discarding `Func` twin per arity, 1 through 16, plus a nullary rung) whose last parameter is
`ref GoFrame` — the frame to register into:

```csharp
defer(Ꮡfd.writeUnlock, ref ᒐ);                      //  Go: defer fd.writeUnlock()
defer(ᴛ1 => fmt.Println(ᴛ1), closeFileˢ, ref ᒐ);    //  Go: defer fmt.Println("Close file")
```

The rungs are generic rather than `params object[]` so a value argument is captured without boxing on
a path that runs on function exit throughout the corpus.

The registration reads as plain `defer` because nothing else in scope claims that name: `defer` is a
Go **keyword**, so no Go identifier can ever be spelled that way, and no C# keyword collides. Its
siblings carry a `ǃ` (U+01C3, a legal C# identifier character where `!` is not) for reasons of their
own: `goǃ` cannot be `go`, which is the root namespace every converted file sits in, and `makeǃ`
cannot be `make`, which is a predeclared Go identifier a package may shadow.

### `defer panic(v)` captures its value at the defer, and the sequence survives it

`panic` is the one built-in emitted as a `throw` **statement** rather than as a call, and that made it
the one built-in the deferred-argument machinery could not see. Argument capture happens in exactly one
place — the argument-list renderer substitutes a temp parameter (`ᴛN`) into the thunk body and hands the
eager expression to the registration — and the `panic` arm returns `throw panic(<expr>)` before reaching
it. So the thunk inlined the ORIGINAL expression and the registration's argument slot was left empty:

```csharp
defer(ᴛ1 => throw panic(errΔ2), , ref ᒐ);   // CS0839: Argument missing
```

The arm now performs the substitution itself, so the value is evaluated at the `defer` and thrown from
the thunk's parameter:

```go
err := fmt.Errorf("first")
defer panic(err)
err = fmt.Errorf("second")     // Go recovers "first" — arguments evaluate at the defer
```

```csharp
defer(ᴛ1 => throw panic(ᴛ1), err, ref ᒐ);
```

Capturing the expression in the thunk body instead — dropping the parameter — also compiles, and is
**wrong** for exactly the shape above: it would report whatever the variable held when the frame
unwound. The same rule reaches `go panic(v)`; `visitGoStmt` now forces the temp-param form for a
built-in callee just as `visitDeferStmt` does, which also repairs `go close(ch)` (a built-in's method
group is generic with `in` parameters and never converted to `Action<T>` — CS1503).

Compiling was only half of it. A deferred `panic` is also the smallest case of a panic raised **by** a
deferred call, and `GoFrame.Run` treated that as the end of the sequence: the panic escaped the loop
and the frame's remaining deferred calls never ran, so the `recover()` thunk registered *before* the
`panic` thunk never saw it. Go continues the sequence — the new panic joins the one unwinding and
becomes what a later `recover()` answers — so `Run` now parks the raised panic where `recover()` reads
it and keeps going, re-raising it at the end only if nothing recovered it. The catch filter is `IsPanic`,
matching the emitted frame's own catch, so a runtime fault in a deferred call is recoverable exactly as
one in the body is; `GoexitException` deliberately fails that filter and still unwinds.

Guarded by `DeferPanicArg`, which output-compares six shapes against `go run`: a plain value, an error
variable reassigned after the defer, a computed expression, a pointer value round-tripping the `any`
boundary and answering a type assertion, a deferred panic replacing one already in flight, and two
deferred panics in one frame (Go keeps the LAST one to run, i.e. the FIRST registered).

### A nested defer scope gets a frame of its own

A `ref struct` cannot be captured by a lambda, and it does not need to be: a function literal that
defers carries its own frame inside the lambda (or local function) it emits as.

Every frame reads under the **same name**, at every nesting depth. A C# lambda or local function may
declare a local spelled like one in the enclosing method — the pre-C# 8 CS0136 rule does not fire —
and the inner declaration precedes every inner use, so an inner `GoFrame ᒐ = default;` simply shadows
the outer one. The one name that *cannot* repeat is the named-result exit **label**: labels do not
shadow (CS0158, *"the label shadows another label by the same name in a contained scope"*), so
`ᒐdone` alone carries a nesting-depth suffix. Both facts were settled by compiling the shapes rather
than reasoning about them.

That is also what makes a **deferred literal that defers on its own account** expressible.
`defer func(){ defer cleanup(); … }()` scopes the inner `defer` to the literal in Go; the literal is
a deferred-call target, so it gets no *recover* scope of its own (its `recover()` recovers the
enclosing function — the whole point of the idiom), but it does get a frame for its own defers, and
the inner registration lands there rather than in the enclosing function's. Guarded by
`DeferFrameScopes`.

### An unrecovered panic crashes the process Go-style: report on stderr, exit code 2
`throw panic(x)` unwinds until some enclosing frame's deferred sequence recovers it. When nothing
does — including in a **goroutine**, since `goǃ` runs the body at the root of its own dedicated thread
with no frame around it and a frame's exception filter only adopts panic-convertible exceptions — the exception reaches
golib's `AppDomain.UnhandledException` backstop (registered in `builtin.InitializeGoLib`). The backstop
matches Go: it writes the report to **stderr** (`panic: <message>`, first mapping runtime-error
exceptions through `RuntimeErrorPanic.TryAsPanic` so e.g. an integer divide by zero reports Go's
`panic: runtime error: …` form) and terminates with **exit code 2** — exactly like an unrecovered Go
panic, minus the goroutine stack-trace lines. It previously printed to *stdout* and called
`Environment.Exit(0)`, which polluted compared output and signaled false success to every caller
(shells, CI, the Phase-4 differential oracle). The behavioral output-comparison harness validates this
differentially: the Go binary is the oracle, so exit codes must **match** (not be zero), stdout must
match, and the **first stderr line** must match — the remainder of Go's panic stderr is a
machine-specific goroutine stack trace, so only the first line is compared. (Guarded by the
`GoroutinePanicExitCode` behavioral test — a goroutine panics unrecovered while `main` blocks on a
channel receive; both binaries must exit 2 with `panic: goroutine boom` as the first stderr line and a
clean stdout.)

### An IMPLICIT divide-by-zero panics with the runtime's OWN value, so it satisfies `runtime.Error`

Go's compiler lowers an integer division to a zero check plus `runtime.panicdivide()`, which panics
with `runtime.divideError` — a value whose dynamic type is the unexported `runtime.errorString`, and
which therefore satisfies the `runtime.Error` interface. go2cs instead lets the CLR raise
`DivideByZeroException` and maps it to a Go panic at the recover boundary
(`RuntimeErrorPanic.TryAsPanic`), so the panic carried only the message TEXT. That is invisible to
code which merely prints the recovered value, but it fails a type assertion — math/bits'
`TestDiv32PanicZero` asserts exactly that:

```go
} else if e, ok := err.(runtime.Error); !ok || e.Error() != divZeroError {
```

`Div32` divides without an explicit zero guard (unlike `Div`/`Div64`, which `panic(divideError)`
themselves), so its panic came from the hardware trap: `ok` was false, `e` was nil, and the test's
`e.Error()` then NRE'd.

golib sits UNDER the converted `runtime` package and so cannot name `divideError`, so the dependency
is **inverted**: golib exposes `RuntimeErrorPanic.IntegerDivideByZeroValue` and the runtime package
registers its own canonical value through a `[ModuleInitializer]` in the hand-owned
`runtime/panicvalues_impl.cs` bridge. An implicit (trapped) divide-by-zero then carries the *same*
value an explicit `panicdivide()` would — which is precisely Go's own invariant — and
`err.(runtime.Error)` resolves through the generated `errorString`→`ΔError` adapter. A converted
program that never links `runtime` keeps the plain-message fallback, which reads and prints
identically and loses only the assertion.

(The fallback path is guarded by the `DivideByZeroPanic` behavioral test, which prints the recovered
value; the `runtime.Error`-typed path is guarded by the committed math/bits Go test suite itself —
behavioral tests build against the baseline `src/core`, which has no `runtime` package, so the typed
form cannot be exercised there.)

### `make([]T, len[, cap])` out-of-range panics are RECOVERABLE, with Go's messages

Go's `makeslice` panics recoverably for a negative or over-allocatable length/capacity — the
recovered value's text is `runtime error: makeslice: len out of range` (or `cap`; probed vs
`go run` — the recovered value is a `runtime.errorString`). golib's make path (the
`slice<T>(nint length, nint capacity, nint low)` constructor) raised
`ArgumentOutOfRangeException`/`OverflowException` for the same inputs — .NET exceptions
`recover()` cannot catch, so a deferred recover never ran and the process died. The constructor
now validates first and throws `RuntimeErrorPanic.MakeSliceLenOutOfRange()` /
`MakeSliceCapOutOfRange()` (recoverable `PanicException`s carrying Go's message text), using
`Array.MaxLength` as .NET's `maxAlloc` equivalent. The same validation class applies to the
hand-owned `internal/bytealg.MakeNoZero` (`bytealg_impl.cs`) — Go's runtime implementation of it
panics `len out of range` before allocating, and strings/bytes `TestRepeatCatchesOverflow`
recovers that panic and matches on `"out of range"` (Phase-4 row R6; `strings.Repeat` of a
near-`maxInt` product reaches `MakeNoZero` after passing Repeat's own overflow pre-checks).
Like the established golib runtime-panic convention, the panic STATE is the message string, not
an `error` value — a recovering type switch takes Go's `case error:` arm only in Go; both sides
converge on the same `err.Error()` text through the `fmt.Errorf("%s", v)` default arm. (Guarded
by the `MakeSlicePanicRange` behavioral test — in-range, negative, huge-length, and huge-capacity
`make` under `recover()`, messages compared vs Go.)

### A panicked C# `string` boxes as Go `string` at golib's boxing boundary
Go's `panic` takes an `any`, so the panicked value's **dynamic type** is observable on the recover
side — `if p != "x"`, `err.(string)`, and `case string:` all test it. Two converted spellings hand
golib a bare C# `System.String` rather than a Go `@string`: a **string literal** (`panic("x")` — the
emission deliberately suppresses the `u8` suffix there, so the argument stays a C# literal) and a
**computed** value from a stub that returns C# `string` (the baseline `fmt.Sprintf` does). Boxed as
`System.String`, such a value matched nothing on the recover side, which compares against `@string`:
sync's `testOncePanicX` reported the self-contradictory `want panic x, got x` (3 tests).

`builtin.panic(object)` — the single boxing boundary for the builtin — now normalizes `string` to
`@string`. Choosing that layer over an emission-site `(@string)` cast is deliberate: the cast would
fix only the literal spelling, while the boundary covers literal, computed, and hand-owned callers
alike. It is a narrow normalization, not a coercion — a NAMED string type keeps its own identity
(a `[GoType("@string")]` wrapper is not a C# `string`), and non-string values are untouched. golib's
own `RuntimeErrorPanic` values are unaffected: those construct `PanicException` directly, and Go's
dynamic type for them is a runtime error, not a string. (Guarded by `PanicRecover`, extended with a
recover-side type switch over a literal, a computed, a variable, a named-string-type, an int, and a
no-panic case, output-compared vs Go.)

### A NIL-POINTER dereference is a RECOVERABLE panic, with Go's message
The same class as the `makeslice` arm above, for the most common Go runtime panic of all. Go's nil
dereference is recoverable and real code depends on it — sync's `TestNilPool` calls `Get`/`Put` on a
`var p *Pool` and asserts that `recover()` catches the panic. The converted equivalent (reading
`Ꮡp.Value` through a nil `ж<T>`, or any nil reference deref in emitted code) raises .NET's
`NullReferenceException`, which `recover()` could not see: the panic escaped past every deferred
recover and surfaced as an unrecoverable host error. `RuntimeErrorPanic.TryAsPanic` — the single
predicate every emitted frame's exception filter (`GoFrame.IsPanic`) and the process-level unhandled
handler share — now maps it to `RuntimeErrorPanic.NilPointerDereference()`, whose text is Go's
verbatim `runtime error: invalid memory address or nil pointer dereference`.

This is faithful rather than lenient: Go recovers a genuine nil-deref bug exactly the same way, and
an *unrecovered* one still prints Go's message on stderr and exits 2 (the unrecovered-panic arm
above). (Guarded by the `NilPointerPanic` behavioral test — a nil pointer-receiver method call, a
nil struct-pointer field read, and a nil map/slice-of-pointer element deref, each under `recover()`
with the recovered text compared vs Go, plus an unrecovered control.)

### Named-delegate and builtin callees keep the lambda form
A zero-argument deferred/goroutine'd call whose callee is a **named func type** (`defer cancel()`
with `cancel context.CancelFunc`, net dial) cannot take the bare trimmed method-group form —
the named type is a DISTINCT C# delegate with no conversion to the `Action` golib expects
(CS1503) — so the invocation stays wrapped: `defer(() => cancelʗ1(), ref ᒐ)` / `goǃ(() => f())`.
A **builtin** deferred WITH arguments (`defer close(returned)`) is generic with `in` parameters,
so its method group neither infers nor converts to `Action<T>`; the temp-param lambda keeps
`defer`'s eager-argument evaluation: `defer(ᴛ1 => builtin.close(ᴛ1), returned, ref ᒐ);` (net
`dial.cs`). (Guarded by `DeferCallOrder`'s stopFn + close(drained) shapes, output-compared vs Go.)

### A value-returning goroutine callee is wrapped in a discarding lambda
Go's `go f(…)` discards `f`'s result. Every `goǃ` runtime overload takes a **void** `Action<…>`
delegate, so a value-returning callee passed as a bare method group binds no overload (CS0407 "no
overload matches the delegate" — x/net/nettest `conntest.go`'s `go chunkedCopy(c2, c2)`, where
`chunkedCopy(io.Writer, io.Reader) error` returns `error`). `visitGoStmt` resolves the callee
signature and, when it returns a value, keeps the invocation inside a lambda so the result is
discarded — an expression-bodied lambda over a value-returning call converts to `Action` (the same
form the variadic path, e.g. `go fmt.Println(…)`, already emits):

```csharp
go chunkedCopy(c2, c2)          -> goǃ((ᴛ1, ᴛ2) => chunkedCopy(ᴛ1, ᴛ2), c2, c2);   // param callee
go q.conn.HandshakeContext(ctx) -> goǃ(ᴛ1 => q.conn.HandshakeContext(ᴛ1), ctx);      // selector method
go c.Close()                    -> goǃ(() => c.Close());                             // nullary callee
```

This parallels the **defer** case. Both `defer` and `goǃ` carry seventeen `Func<…, TResult>` twins
alongside their `Action` rungs, so a value-returning method group *with arguments* binds either
directly; neither has a nullary `Func<TResult>` rung, so a nullary value-returning callee takes the
`() => call()` discard on both sides (see *Deferred calls whose callee returns a value* below). The
converter's remaining discarding wraps cover the shapes a method group cannot express here — a
value-returning callee reached through a selector or parameter, and the CS1113
value-receiver-extension case below, whose reason is delegate *creation* rather than the callee's
result. Func-literal callees and `void`-returning method groups are untouched (`goǃ(() => { … })`,
`goǃ(emit, out)`).

The runtime's `Func` twins are what make one shape expressible at all: a func-literal callee that
*returns a value*. `go func(ln Listener) (retErr error) { … }(ln)` (net `sendfile_test`) emits its
literal with an explicit `error` return type, because a named result set by a `defer` needs one, and
no `Action<Listener>` overload accepts it (CS8934). Wrapping it in the converter would mean
suppressing the literal's own return type and rewriting its trailing `return retErr;` — rewriting a
correct emission to fit a runtime gap. The `Func` siblings fix that shape, and every other one, at
the seam where Go's rule actually lives: `go f(…)` discards results, for **any** `f`. A void lambda
or method group cannot bind `Func<TResult>` at all, so no existing call site is affected. (Guarded by
the `GoStmtValueReturn` behavioral test — value-returning nullary, single-, multi-param, multi-result
and func-literal goroutine callees, output-compared vs Go.)

A **VALUE-receiver method callee** forces the same lambda forms even when void and
arity-matching: every Go named type emits a C# struct and the method an extension on it, and C#
forbids constructing a delegate from an extension method over a **value-type receiver** (CS1113 —
net/http/httputil's `go spc.copyToBackend(errc)`, `switchProtocolCopier`). So
`goǃ(spcʗ1.copyToBackend, errc)` becomes `goǃ(ᴛ1 => spcʗ1.copyToBackend(ᴛ1), errc)` and a nullary
`go vs.ping()` keeps its invocation (`goǃ(() => vsʗ1.ping())`). The receiver snapshot (`spcʗ1`)
still evaluates at go-statement time. An INTERFACE-receiver method group is excluded (a genuine C#
instance method binds delegates fine), and pointer receivers keep the box-group machinery
(`pointerReceiverBoxMethodGroup` — `ж<T>` is a class, so its group is delegate-legal). (Guarded by
`GoStmtReceiverLambda`'s `valueSender` arms — value-receiver argument and nullary go-statements
with blocking-receive completion proof, output-compared vs Go.)

### A func-literal ARGUMENT of a deferred call hoists its captures before the call
When a deferred call's **callee** is itself a func literal (`defer func() { … }()`), that literal's
lambda-capture snapshots (`var sʗ1 = s;`) are threaded to a builder emitted *before* the `defer(…)`
call. But when the deferred callee is an ordinary call whose **argument** is a capturing func literal —
x/net/nettest `conntest.go`'s `defer once.Do(func() { stop() })` — the argument literal's snapshot
declarations were dumped inline into the deferred call's argument list, an invalid statement
mid-expression (`defer(Ꮡonce.Do,` `var stopʗ1 = stop;` `() => …)` → CS1001/CS1002/CS1003/CS1026).
The hoist sink (`lambdaContext.deferredDecls`) is now provided **unconditionally** in `visitDeferStmt`,
not only for the func-literal-callee case, so `convFuncLit` (reached via convCallExpr → convExprList →
the argument's `LambdaContext`) routes any argument literal's captures to it, and they are emitted before
the call — the guard's `defer run(func(){ pf.x = 77 })` shape:

```csharp
var pfʗ1 = pf;
defer(run, () => {
    pfʗ1.Value.x = 77;
}, ref ᒐ);
```

The empty builder is inert for a deferred call with no capturing func-literal argument (zero golden
churn — the behavioral corpus is byte-identical), and a deferred call whose own arguments are plain
captures keeps its existing pre-call `generateCaptureDeclarations()` emission. (Guarded by the
`FuncLitArgCapture` extension case 14 — a func literal passed as the argument of a deferred `run(…)`
call capturing a local pointer, whose deferred write lands through the shared pointer box, output-compared
vs Go.)

### Defer/go EAGER arguments follow the enclosing closure's capture renames
Go evaluates a deferred (or spawned) call's function value and arguments **at statement time, in the
enclosing scope**. The defer/go emission enters its own lambda-conversion state (its callee snapshots
need a fresh remap set), but that fresh state previously hid the ENCLOSING lambda's capture renames
while the eager arguments rendered: inside an IIFE that snapshot-captured a heap-boxed outer local
(`var baseʗ1 = @base;`), the argument of `defer func(t Tally) { … }(base)` emitted the raw ref-local
`@base` — uncapturable in the IIFE's C# lambda (CS8175) — with the snapshot left declared but unused;
where the raw name IS capturable (a reference-typed local — net/http `transport.go`'s
`defer close(didReadResponse)` inside a `go func() { … }` lambda), it silently bypassed the snapshot
every other read in that body uses. `visitDeferStmt`/`visitGoStmt` now enter through a **seeded**
variant (`enterDeferGoLambdaConversion`) that copies the enclosing lambda's renames into the fresh
state, so the arguments render exactly like any other expression in the enclosing body:

```csharp
var baseʗ1 = @base;
((Action)(() => {
    GoFrame ᒐ = default;
    try {
        defer((Tally t) => {
            report(deferredˢ, t, 4);
        }, baseʗ1, ref ᒐ);
    }
    catch (Exception ᒐex) when (GoFrame.IsPanic(ᒐex, out PanicException? ᒐp)) { GoFrame.Capture(ᒐp); }
    finally { ᒐ.Run(); }
}))();
```

`prepareStmtCaptures` still OVERRIDES the statement's own captured-callee entries afterward (their
defer-time snapshots), and a function-level defer/go — no enclosing lambda — is untouched (the seed
set is empty). Whole-stdlib footprint: exactly one file, net/http `transport.cs`, where the deferred
`close` argument becomes the goroutine lambda's `didReadResponseʗ1` (semantically neutral there — both
names alias one channel object; the fix matters for ref-local-boxed value locals). (Guarded by the
`DeferArgEnclosingCapture` behavioral test — a heap-boxed struct local passed eagerly to a deferred
func literal, a deferred NAMED callee, and a go-statement literal, each inside an IIFE, with the
mutations landing on the deferred copies and the source read back untouched, output-compared vs Go.)

### A func-literal ARGUMENT inside an `if`/`for` condition hoists its captures before the statement
The same capture-snapshot hazard occurs when a capturing func literal is passed as a call argument
**inside a condition**. `go/types` is dense with this shape — `underIs(t, func(u Type) bool { … })`,
`typeSet().is(func(t *term) bool { … })` — and the literal's snapshot declarations (`var suʗ1 = su;`)
are statements, invalid inside the condition expression. `visitExprStmt` / `visitAssignStmt` already
route such decls to a pre-statement hoist buffer (`v.hoistedDecls`), but `visitIfStmt` and
`visitForStmt` converted the condition with `convExpr(cond, nil)` and no hoist target, so the decls
were dumped inline into the condition (`if (tpar.underIs(` `var suʗ1 = su;` `(ΔType u) => { … }))` →
CS1003/CS1026/CS1002/CS1022/CS1513, ~63 errors across `go/types` alone). Both statement emitters now
convert the condition into a hoist buffer and write any collected decls on their own lines **before**
the `if`/`for`, mirroring `visitExprStmt`:

```csharp
ΔType su = default!;
var suʗ1 = su;
if (tpar.underIs((ΔType u) => {
    …
    if (suʗ1 != default!) { u = match(suʗ1, u); … }
})) { … }
```

The condition is converted **after** an `if`/`for` init clause (preserving capture-counter ordering),
and the `if`-with-init sub-block hoists between the init and the `if`. The traditional `for` reuses the
existing `ForVarInitMarker` slot — the hoisted condition decls are emitted at the same pre-`for` position
as the for-init heap allocations. The hoist buffer is empty for a condition with no capturing func-literal
argument, so the behavioral corpus is byte-identical; the only stdlib deltas are five `go/types` files
(`under.cs`, `builtins.cs`, `expr.cs`, `index.cs`, `instantiate.cs`) and one `crypto/tls`
`slices.ContainsFunc` call. This clears the **syntax-error layer** in those files (`go/types` had ~63
`CS100x`/`CS1026` from this one construct); it does not by itself green `go/types`, which compiles far
enough afterward to surface a deeper layer of latent semantic defects (a `map[token.Token]func()`
mis-lowered to a malformed explicit-interface `IDictionary`/`ICollection` implementation, named-slice
wrappers not satisfying `IArray.Source`, `token` resolution) — the frontier moves from syntax to
semantics, "progress, not regression." (Guarded by `FuncLitCaptureInCondition` — a func literal
capturing an enclosing map, passed as an argument inside a plain `if` condition, an `if` condition with
an init clause, a traditional `for` condition, and a while-style `for` condition, all output-compared vs
Go.)

### A func-literal ARGUMENT inside a return expression hoists its captures before the `return`

The third statement position with the same hazard: a capturing func literal passed as a call argument
**inside a return expression** — net/http's `findHandler` returns
`HandlerFunc(func(w ResponseWriter, r *Request) { … allowedMethods … }), "", nil, nil`, and traceviewer's
`MainHandler` returns `http.HandlerFunc(func(){ … views … })`. A **direct** func-literal result threads
`lambdaContext.deferredDecls` (the go/defer/return channel in `convFuncLit`), but a literal nested as a
call **argument** falls back to the pre-statement hoist sink, which `visitReturnStmt` never provided —
the snapshot declaration was dumped inline inside the return expression (10 syntax errors in `server.cs`,
a 4-error cascade in traceviewer). `visitReturnStmt` now provides the same hoist buffer as
`visitExprStmt`/`visitIfStmt`/`visitForStmt` and splices it before the `return` through its existing
`DeferredDeclsMarker` slot (ahead of any deferred tuple-deconstruction temps):

```csharp
var allowedʗ1 = allowed;
return (wrap((@string msg) => {
    fmt.Println(allowedʗ1[0] + ":" + msg, len(allowedʗ1));
}), "label", default!);
```

The buffer is empty for a return with no capturing-literal argument, so the behavioral corpus is
byte-identical. (Guarded by `ReturnTupleFuncLitArg` — a slice-capturing literal as a call argument inside
a three-result return tuple, and a map-capturing one inside a single-result return, output-compared vs
Go.)

### A func literal inside a RANGE expression hoists its captures before the loop

The fourth statement position, and the one the table-driven test idiom lands on constantly:

```go
for _, test := range []struct {
    desc string
    f    func()
}{
    {desc: "WithCancel(bg)", f: func() { c, cancel := WithCancel(bg); cancel(); <-c.Done() }},
    …
} {
```

The literal's snapshot declaration (`var bgʗ1 = bg;`) is a **statement**, and the composite-literal
element position it would be written into is pure expression context. `visitRangeStmt` converted the
range expression with `convExpr(rangeStmt.X, nil)` — no hoist target — so the decl was dumped inline
after the `f:` argument name, and the whole file died in a syntax cascade (`context`'s `x_test.cs`:
CS1003/CS1026/CS1002/CS1513/CS0106 ×195, from `TestAllocs` and `TestCause` alone).

`visitRangeStmt` now converts the range expression into a hoist buffer and **splices** the collected
decls in at the statement's own start — it records that offset before conversion and inserts there
afterwards (`spliceOutput`, the positional twin of `replaceMarker`), because the `foreach` header is
emitted much further down through a dozen different arms:

```csharp
    var bgʗ1 = bg;
foreach (var (_, test) in new TestAllocs_type[]{
    new(desc: "WithCancel(bg)"u8, f: () => { var (c, cancel) = WithCancel(bgʗ1); … }),
    …
}.slice()) {
```

The buffer is empty for a range expression with no capturing func literal, so the behavioral corpus is
byte-identical. (Guarded by `RangeExprFuncLitCapture` — slice- and map-capturing literals as struct
fields of a ranged composite literal, plus a bare `[]func(string)` element list, output-compared vs Go;
its A/B reproduces the cascade exactly.)

**The class, stated once:** `visitExprStmt`, `visitAssignStmt`, `visitIfStmt`, `visitForStmt`,
`visitReturnStmt`, `visitValueSpec` and now `visitRangeStmt` each provide the pre-statement sink. The
statement kinds that still do not — a `switch` tag, a `select` comm-clause, a bare send — have no
demonstrated corpus site, and each would repeat this failure exactly. They are deliberately not widened
speculatively: the tell that one has been reached is a syntax cascade whose first error sits on the line
after a `<name>:` argument label.
### The enclosing statement's hoist buffer does NOT extend into a literal's BODY

Those four positions all work the same way: the enclosing statement opens a hoist buffer, and a
capturing func literal inside it writes its snapshot declarations there. The buffer is a valid position
for **that literal's own** captures — they name bindings from the enclosing scope, which exists before
the statement. It is not a valid position for anything the literal's **body** hoists: a statement inside
the body opens its own buffer, and a nested literal whose captures name a binding declared *inside* this
body would be declared outside it.

`time`'s `BenchmarkStaggeredTickerLatency` nests three levels of `b.Run(…, func(b *testing.B){…})`. The
middle literal `make`s a `stats` slice; the innermost `go func(…)` captures it. The snapshots landed in
the OUTER literal's `b.Run(…)` statement buffer — two blocks above the declaration:

```csharp
for (nint tickersPerP = 1; …; tickersPerP++) {
    nint tickerCount = gmp * tickersPerP;
    var statsʗ1 = stats;                      // CS0103 — `stats` is declared below, inside bΔ2
    bΔ1.Run(…, (ж<Δtesting.B> bΔ2) => {
        var stats = new slice<…>(tickerCount);
```

`convFuncLit` now detaches `v.hoistedDecls` for the duration of the body walk and restores it after, so
a nested hoist can only reach a position inside the body. The literal's own captures are unaffected —
they are flushed before the body is converted, while the enclosing buffer is still installed. (Guarded
by `FuncLitArgCapture` case 15.)

Handling Go `defer` / `panic` / `recover` is what the FRAME above is for: the body is emitted inline in `try`/`catch`/`finally` beside a [`GoFrame`](https://github.com/ritchiecarroll/go2cs/blob/master/src/core/golib/GoFrame.cs) local that holds this call's defer list. `panic` is the global [`panic`](https://golang.org/pkg/builtin/#panic) built-in and `recover` the global [`recover`](https://golang.org/pkg/builtin/#recover) (both a `using static go.builtin`). A function that neither directly nor indirectly (through a deferred lambda) uses `defer`/`recover` gets no frame at all -- the scope is per function, so a `main` that merely calls `f()` is emitted as a plain method body.

* **Named results + defer.** See *The named-result form* above: the results are declared before the `try` and read back after the `finally`, and every exit inside leaves through a `goto`.
* **IIFEs.** An immediately-invoked function literal that itself uses defer/recover carries its own frame inside its own delegate-cast invocation (`((Action)(() => { GoFrame ... }))()`), so its defers are its own -- while its `recover()`, being a static call, still reads the one panic slot.
* **A `return` emits against ITS OWN function's results, not the enclosing function's.** A bare `return` in a function with named results emits `return (n, ok);` (the named results). A *nested function literal* must be converted against its **own** signature -- otherwise a bare `return` inside a **void** closure would inherit the enclosing function's named results and emit `return (n, ok);` into a `void` lambda (CS8030, "anonymous function converted to a void-returning delegate cannot return a value"). Runtime `mprof.goroutineProfileWithLabelsSync` (named `(n, ok)`) passes `forEachGRace(func(gp1 *g) { ...; return; ... })` -- the void closure's bare returns must stay `return;`. The return signature is tracked separately from `currentFuncSignature` (which stays the *enclosing* function's, so the receiver/parameter detection still resolves a **captured** pointer parameter -- an outer parameter -- correctly): `convFuncLit` sets a dedicated return-signature to the literal's own signature with save/restore, and `visitReturnStmt` emits results against it. (Guarded by the `ClosureBareReturnNamedResults` behavioral test -- a void closure with bare returns nested in a named-results function, output verified vs Go; cleared runtime's 4 CS8030.)
* **Return-type INFERENCE is not a concern.** An inline body returns against the METHOD's own declared result type, so no inference runs over the return statements and the two shapes that would defeat one cannot arise: every return carrying an untyped `default!` (Go `nil` -- syscall's `getProcessEntry`), and returns of two unrelated concrete types sharing only the declared interface (go/parser's `parseTypeName` returning `&ast.SelectorExpr{...}` beside a plain `*ast.Ident`). Both are pinned as guards: `DeferTypelessReturns` (unnamed results, a defer, and every return carrying nil) and `DeferInterfaceReturn` (a defer/recover func returning `Shape` via `Circle` vs `Square`, plus a heterogeneous `(Shape, bool)` tuple return).

### A BLANK result mixed with a named one still needs the named-return-defer handling

Go permits mixing the blank identifier and real names in one result list — `func parse(s string, flags Flags) (_ *Regexp, err error)` (regexp/syntax) — and deferred code can still mutate `err`. The detection required **every** result to be named and non-blank, so the first `_` rejected the whole signature and the function fell back to the unnamed-result form, whose catch arm returns Go's zero results. On a recovered panic that arm returned `default!`: the deferred handler assigned `err`, the catch arm discarded it, and the function reported **(nil, nil)** — a *successful* parse of an expression that must fail. Every "expression too large" / "nesting depth exceeded" input (`a{100000}`, `strings.Repeat("(", 1000)+…`) came back as a valid parse, and the caller's `dump(re)` on the nil pointer then panicked.

A blank result is a real result **slot** — only a `return` statement can write it, the body cannot name it — so it needs a declaration alongside the named ones. C#'s `_` is the **discard**: declaring it would capture every later `_ = expr` in scope, and two blank results would collide outright, so `namedResultName` mints a generated slot name (interned per result object, so the declaration, each return's assignment and the post-defer read all agree):

```csharp
internal static (ж<Regexp>, error err) parse(@string s, Flags flags) {
    ж<Regexp> _ᴛ1 = default!;          // the BLANK slot
    error err = default!;
    GoFrame ᒐ = default;
    try {
        defer(() => { … err = new ΔErrorжerror(…); … }, ref ᒐ);   // recover assigns the named result
        …
        (_ᴛ1, err) = (literalRegexp(s, flags), default!); goto ᒐdone;   // `return literalRegexp(s, flags), nil`
    }
    catch (Exception ᒐex) when (GoFrame.IsPanic(ᒐex, out PanicException? ᒐp)) { GoFrame.Capture(ᒐp); }
    finally { ᒐ.Run(); }
    ᒐdone: return (_ᴛ1, err);          // reads BOTH slots after the defers ran
}
```

A result list that is entirely **unnamed** (`func f() (int, error)`) or entirely blank keeps the plain form: there is nothing deferred code could mutate, and Go likewise returns the zero results after a recover. Go forbids mixing named and unnamed results, so seeing one truly unnamed result settles the whole signature. (Guarded by the `NamedReturnDefer` extension — a `(_ *box, err error)` function whose recover sets `err`; the pre-fix converter compiles it and returns `(nil, nil)`.)

### Function-literal named results

A func **literal** with named results declares them at the top of its emitted block, zero-initialized — Go's semantics for `next = func() (v1 V, ok1 bool) { …; return }` (the `iter.Pull` shape): a bare `return` yields the named results as currently assigned, so the lambda emits `() => { V v1 = default!; bool ok1 = default!; …; return (v1, ok1); }`. Without the declarations the emitted tuple referenced undeclared names (CS0103 — the `iter` package's last wave-1 errors). Two interactions: a named-results literal whose *first* statement is a bare `return` must NOT collapse to an expression-bodied lambda (the names exist only as block declarations), and the `namedReturnDefer` path (named results that deferred code mutates) keeps its own arrangement — declarations *before* the `try`, returned after the `finally`. Declarations reuse the shadow-aware naming, so a literal result shadowing an outer local renames consistently in both the declaration and the return (`nΔ1`). (Guarded by the `FuncLitArgCapture` extension — bare returns with assigned and zero named results, plus the first-statement-bare-return shape, values vs Go.)

Because a named result lives in the literal's OWN scope, a reference to it in the body is the result, never an outer-scope capture — so named results are excluded from the lambda-capture set (`convFuncLit`) exactly as parameters are. text/template's `readFileFS` returns `func(file string) (name string, b []byte, err error)`, whose closure captures the enclosing `fsys` AND writes `b` via the captured tuple call `b, err = fs.ReadFile(fsys, file)`. Because the closure genuinely captures `fsys`, the capture analysis ran and mis-flagged `b` too — hoisting `var bʗ1 = b;` into the enclosing function, where `b` does not exist (CS0103), and renaming the body's `b` to the captured `bʗ1`. Filtering the named-result names out of the capture set (alongside the parameter names) leaves `b` a plain in-block declaration. (Guarded by `CrossPkgUser`'s `makeScanner` — a captured closure returning named results, one written via a tuple call whose RHS uses the capture, output-compared vs Go; crypto/x509 and html/template shared the same latent shape.)

### Deferred calls whose callee returns a value take the lambda form
The no-arg defer arm passes a bare method group (`defer(k.Close, ref ᒐ)`) only when the callee returns VOID -- an error-returning method (`defer k.Close()`, registry `Key.Close`) is a `Func<error>` method group that cannot bind the golib `defer(Action, ref GoFrame)` (CS1503). The lambda form discards the result, exactly Go's deferred-call semantics:
```csharp
defer(() => hʗ1.close(), ref ᒐ);
```
Guarded by `DeferTypelessReturns`.

### Deferred pointer-receiver nullary calls bind the box method group
`defer conf.releaseSema()` with `conf *resolverConfig` (net nss.go / dnsclient_unix.go) trimmed to the deref-alias method group `Ꮡconf.Value.releaseSema` — a struct VALUE against the [GoRecv] `ref` extension, which cannot create a delegate (CS1113). The emission binds the BOX method group instead:
```csharp
defer(Ꮡconf.releaseSema, ref ᒐ);
```
The `ж<T>` overload is class-typed and delegate-legal, and the method-group conversion captures the receiver when the delegate is created — exactly Go's binding time. Mirrored in the go-statement arm. Gated to methods declared DIRECTLY on the pointee — a PROMOTED method (net interface.go's `defer zc.Unlock()`, declared on the embedded `sync.RWMutex`) has no extension on the outer box (CS1061) and keeps the lambda emission — and to void results (a `Func<>` group binds neither `defer(Action)` nor `go(Action)`). (Guarded by `DeferCallOrder`'s `acquireAndWork`, output-compared.)

The same box-method-group emission also covers a **value receiver** whose type is exactly the pointer-receiver's pointee — `defer b.deck.reset()` (runtime/pprof; also database/sql, log/slog), where `deck pcDeck` is a value FIELD reached through a nested selector and `reset` has a `*pcDeck` receiver, so Go auto-takes `&b.deck`. The original arm required the receiver be an already-pointer *ident*; the value case renders `&receiver` through the shared address machinery (the same `&ast.UnaryExpr{AND}` → `convUnaryExpr` synthesis used elsewhere) — a boxed base gives the aliasing field-ref `Ꮡb.of(profileBuilder.Ꮡdeck)`, an escaping value local gives its box `Ꮡx`, a plain value gives the `Ꮡ(value)` copy — then binds the method: `defer(Ꮡb.of(profileBuilder.Ꮡdeck).reset, ref ᒐ)`, the ж<pcDeck> overload captured at defer time and mutating the real field. Gated the same way (void result, a NAMED value type whose RecvGenerator box overload exists, matching the pointee exactly so a promoted/embedded method is excluded). (Guarded by the `DeferValueFieldPtrReceiver` behavioral test — a pointer receiver deferring `b.c.reset()` on a value field, and a pointer local deferring the same in a closure, with the reset observed through the same box after return, output-compared vs Go.)

### A deferred pointer-receiver method on an escaping value local captures by-box, not by-copy
The emission above binds the box (`Ꮡstate.free`) for a `defer state.free()` on a value local — but the CAPTURE analysis must cooperate. `defer`/`go`/closure bodies are lambda-conversion scopes: a variable used inside them that escapes to the heap is normally snapshot-copied into a `var stateʗ1 = state;` declaration so the C# closure captures a value, not an uncapturable ref-local. For an escaping value local used **only** as the receiver of a pointer-receiver method call (`state` a `handleState` value, `free` a `*handleState` method — log/slog handler.go's `defer state.free()`), that snapshot is doubly wrong: the address-taking is *implicit* (Go auto-takes `&state`), so the emission still binds the box — but of the *snapshot name* `Ꮡstateʗ1`, which is a plain value with no `Ꮡ` companion:
```csharp
ref var state = ref heap<handleState>(out var Ꮡstate);
state = h.ch.newHandleState(buf, true, " "u8);
var stateʗ1 = state;         // snapshot copy — WRONG
defer(Ꮡstateʗ1.free, ref ᒐ);        // Ꮡstateʗ1 never declared → CS0103
```
The capture analysis now recognizes this implicit address-of (a value receiver of a pointer-receiver method, matching the pointee exactly and NAMED — the same guard the emission uses) as a reason to treat the local as a **box-ref var**, exactly like an explicit `&state`: it skips the snapshot, and the emission binds the original heap box:
```csharp
ref var state = ref heap<handleState>(out var Ꮡstate);
state = h.ch.newHandleState(buf, true, " "u8);
defer(Ꮡstate.free, ref ᒐ);          // binds the live variable's box
```
This is not merely a compile fix — a value snapshot is taken at defer time, so it would miss any mutation the body makes to `state` before the deferred call runs; binding `Ꮡstate` matches Go's semantics of deferring against the *live* variable. Gated to an escaping local (a non-escaping one has no `Ꮡ` box and keeps the compiling `Ꮡ(copy)` form) used as a value receiver whose type is exactly the method's pointer-receiver pointee (an already-pointer receiver's box group is the pointer variable itself, whose snapshot name IS declared, so it is excluded). The same generalization silently corrects the closure form (`func(){ x.mutate() }` on an escaping value local previously mutated a lost copy — go/types conversions.go/typeset.go) and removes now-dead `var xʗ1 = x;` snapshots wherever the box was already used. (Guarded by the `DeferHeapLocalPtrMethod` behavioral test — a value local deferring a pointer-receiver method, mutated after the defer, with the deferred method observing the final value, output-compared vs Go.)

The same box-ref treatment covers a **promoted** pointer-receiver method reached through **value embeds** — `lazyCert.Do(…)` on `var lazyCert struct { sync.Once; v *Certificate }` (crypto/x509 `AppendCertsFromPEM`): Go takes `&lazyCert.Once`, an address into the variable's own storage, so the closure must share the original variable. The detection resolves the call through `info.Selections` and walks the selection's embedded-field index path — only **value** embeds along the path root the address at the variable (a **pointer** embed re-roots it at that pointer's target, where the snapshot, which copies the pointer, stays sound). Emission then renders the promoted call through the box's field projection and field uses through the box read; the snapshot form had referenced a never-declared snapshot box (`ᏑlazyCertʗ1`, CS0103) *and* divorced the closure's writes from the original:
```csharp
ᏑlazyCert.of(AppendCertsFromPEM_lazyCert.ᏑOnce).Do(() => {
    (ᏑlazyCert.Value.v, _) = ParseCertificate(certBytesʗ2);
    …
});
return (ᏑlazyCert.Value.v, default!);
```
A variable marked box-ref is also never snapshot-copied by a **nested** literal — the box is a plain reference local that closures at any nesting depth capture directly, so the per-layer `var lazyCertʗ2 = lazyCertʗ1;` chains disappear with it. (Guarded by the `ClosureEmbeddedPromotedPtrMethod` behavioral test — an anonymous-struct local with a value embed whose pointer-receiver method is called from sibling and nested closures interleaved with field writes, cumulative counts observed vs Go.)

The same box-ref treatment covers a pointer-receiver method on a **value-struct FIELD projection** of the escaping local — `defer p.fake.setLines()` (go/internal/gcimporter iimport.go/ureader.go), where `p` is an escaping `iimporter` value local and `setLines` a `*fakeFileSet` method on the value field `p.fake`: Go takes `&p.fake`, an address INTO `p`'s own storage, and the emission renders it through the box's field view — but the snapshot path renamed the base first, referencing a never-declared snapshot box:
```csharp
var pʗ1 = p;                                  // snapshot copy — WRONG
defer(Ꮡpʗ1.of(iimporter.Ꮡfake).setLines, ref ᒐ);     // Ꮡpʗ1 never declared → CS0103
```
The capture analysis now matches the single field-projection receiver (a FIELD selected on the var's own value-struct storage — the same `&m.field` form `lambdaBoxRefAddressForm` emits — whose type is exactly the method's pointer-receiver pointee and NAMED) as the same implicit address-of, marks the local box-ref, and the defer binds the live box's field view:
```csharp
defer(Ꮡp.of(iimporter.Ꮡfake).setLines, ref ᒐ);
```
As with the direct case this is a write-visibility fix, not merely a compile fix: gcimporter registers the defer *before* importing (which populates `p.fake.files`), so a snapshot would flush an empty file set. The same generalization corrects deferred **closures** that read such a variable — go/parser's `defer func(){ …; err = p.errors.Err() }()` snapshot-copied `p` at defer time, so the closure read the parser state from *before* parsing (errors always empty); box-ref renders those reads `Ꮡp.Value.errors…` against the live variable. A deeper chain (`p.a.b.m()`), a pointer field hop, or a method promoted through the field's own embeds keeps the existing snapshot handling. (Guarded by the `DeferHeapFieldPtrMethod` behavioral test — a heap-boxed value local deferring a pointer-receiver method on its value field, with lines appended after the defer observed by the deferred flush, output-compared vs Go.)

## Expression Switch Statements
Go expression-based `switch` statements are flexible: cases do not fall through automatically (no `break` needed), and the `fallthrough` keyword runs the next case body bypassing its expression. Based on the [Manual Tour of Go Conversions](https://github.com/ritchiecarroll/go2cs/tree/master/src/Examples/Manual%20Tour%20of%20Go%20Conversions), converting to `if / else if / else` is the best choice for most cases. When every case label is a C# **compile-time constant** and there is no `fallthrough`, a traditional C# `switch` works. "Constant" here means a C# `const` — a literal, a computed literal expression (`a + b`), or a *typed* basic-type const — not merely a Go constant. A case label that references a plain variable, a struct field (`case frame.fp`), an *untyped* / named-type / cross-package const emitted as `static readonly` (`case goarch.PtrSize`), or an address-of expression (`case &g`) is **not** a C# constant, so a C# `switch` case label there is invalid (CS9135 / CS0150). Such switches fall back to the `if / else if` form comparing the tag with `==` (a temp captures the tag: `var exprᴛ1 = tag; if (exprᴛ1 == frame.fp) …`). The same constant-vs-runtime-value test also chooses `is` (constant pattern) vs `==` for a single-value case within the if-else form. A Go `break` inside a case exits the *switch* (skipping the rest of the case); in the `if / else if` form there is no enclosing C# switch for it to target (CS0139), so a case body that contains such a break is wrapped in a `do { … } while (false)` — the break exits that one-shot loop, i.e. the case. The wrap is emitted only for a case whose body actually has a switch-targeting `break` (one not caught by a nested loop/switch/select), so every other case is unchanged. (A `break` inside a *nested* loop within the case still targets that loop, as in Go.) For cases that use `fallthrough`, the cases are expanded to standalone `if` statements with a local fall-through flag and `goto` to handle break-style exits — the most complex (and least pretty) scenario. In that if-chain form a **trailing `default:` reached via fallthrough** is emitted as a *guarded* `if (fallthrough || !match) { … }` — the guard is needed so the default does not run after a matched-but-non-fallthrough case, but C# cannot prove it always executes. So when such a guarded-default switch is the last statement of a **value-returning** function and every case is terminal, C# reports CS0161 ("not all code paths return a value") even though the Go `default` makes the switch exhaustive (runtime `startpanic_m`). Because a guarded-terminal-default switch cannot be legally followed by reachable Go code (it always returns/exits), the converter emits an unreachable `return default!;` after the if-chain to satisfy C#'s definite-return analysis — gated on the enclosing function/literal actually returning a value (via its own return signature), so a `void` function or a switch that isn't terminal is unaffected. (Guarded by the `SwitchFallthroughDefaultReturn` behavioral test; cleared runtime's CS0161.) A comparison case may use a C# relational/constant pattern (`case {} when x is < 0`) only when the compared-to operand is a C# compile-time constant; for a variable (`case x == y`) or a `static readonly` const (untyped/cross-package), it falls back to a `when` guard (`case {} when x == y`) — a relational pattern there is invalid (CS9135).

### A switch on a `static readonly` constant tag lowers to if-else
A switch TAG that is itself a constant emitted as `static readonly` -- an untyped const's `UntypedInt` wrapper (`switch goarch.PtrSize`, reflect abi.go) or a `uintptr`-struct const -- cannot govern a C# switch: the int case labels are not constants OF the wrapper struct type, and the `is` constant-pattern lowering fails the same way (CS9135). The recorded tag type is no help (go/types records the untyped constant's DEFAULT type in tag position), so the gate is on the object resolution: a constant-valued tag that is not a true C# `const` forces the if-else form (wrapper `==` operators) and disables the `is` pattern:
```csharp
var exprᴛ1 = CrossPkgLib.Precision;
if (exprᴛ1 == 1) {
```
A variable tag stays switchable. Guarded by `CrossPkgUser` / `CrossPkgLib`.

### A leading constant-true case stays opaque to the compiler
Go's `switch { case true: ... case cond: ... }` (time parseStrictRFC3339 deliberately disabling its strict checks) compiles the LATER cases as dead code; a foldable `when true` makes C# reject them outright (CS8120). A constant-true case condition on a NON-LAST clause therefore emits the golib `ᐧᐧ` marker -- a `static readonly bool` the compiler cannot fold:
```csharp
case {} when ᐧᐧ: {
```
The marker is deliberately SEPARATE from the const `ᐧ` switch governor: that const's foldability is itself load-bearing (`case ᐧ when ...` label patterns need a constant, and an infinite `for (...; ᐧ ;...)` relies on the fold for reachability proofs -- CS9135/CS0161 when it was made readonly in place). Guarded by `ExprSwitch`.

### No constant pattern against a named-numeric wrapper
A constant expression whose CONTEXTUAL type is a wrapper struct -- golib `uintptr` or any `[GoType("num:...")]` named numeric (time's `Duration`) -- can never be a C# constant, so no constant/relational pattern can compare against it: `d is >= 0` types the literal 0 as Duration (CS9135). The lowering keeps the plain operator form (`d >= 0`, the wrapper's operators). Guarded by `ExprSwitch` (the `pace` switch).

### An index-expression case label falls back to equality
A case label that INDEXES a package-level array/slice variable (`case Typ[UntypedNil]:` — go/types
operand.go, where `Typ` is the universe `*Basic` array) is a runtime value, never a C# constant. The
single-value `is` form is doubly broken there: C# parses `exprᴛ1 is Typ[UntypedNil]` in pattern position
as an array TYPE (CS0246 + CS0270). `canUsePatternMatch` rejects an `*ast.IndexExpr` label the same way
it rejects a non-constant identifier/selector, so the clause takes the `==`/`AreEqual` comparison the
multi-value arm already produced:
```csharp
if (AreEqual(exprᴛ1, Typ[UntypedNil])) {   // NOT `exprᴛ1 is Typ[UntypedNil]`
```
(Guarded by the `IndexExprCaseLabel` behavioral test — single- and multi-label clauses indexing a
package-level array var, output-compared vs Go.)

### Literal case labels under a named-type tag compare through a cast

A tagged switch whose tag type is a NAMED (non-interface) type — net/http's
`func (code socksReply) String()` switching on `code` — renders the tag as a `[GoType]` wrapper
struct. An untyped-LITERAL label adopts the tag's named type in Go (go/types records it on the label
expression), but its C# render is a bare literal of the UNDERLYING type, which can neither be a
constant pattern (`exprᴛ1 is 0x01` — CS9135, constant pattern against the wrapper) nor compare bare
(`exprᴛ1 == 0x01` is ambiguous between the wrapper's `==` and the underlying's built-in `==`, both
reachable through the wrapper's two-way implicit operators — the same ambiguity family as the
named-string consts). Two converter pieces:

1. the pattern-match decision excludes any named-wrapper tag (`tagIsNamedWrapper`, beside the
   existing `namedTypes`/`tagIsStaticReadonlyConst` gates — those could not catch the mixed
   const-ident + literal switch, because the per-label screening short-circuits once `allConst`
   goes false and never reaches its named-type check);
2. a CONSTANT label that is not an ident/selector/conversion-call (those already render AT the
   wrapper type) casts to the tag type:

```csharp
var exprᴛ1 = code;
if (exprᴛ1 == socksStatusSucceeded) {     // named-const label — no cast
    return "succeeded"u8;
}
if (exprᴛ1 == (socksReply)(0x01)) {       // literal label — cast to the tag type
    return "general SOCKS server failure"u8;
}
```

This also repairs the ALL-literal switch over a named type, which previously emitted the ambiguous
bare `==` form. Full-stdlib footprint: 12 files (socks_bundle, archive/zip, encoding/xml,
go/printer, go/types, internal/poll, syscall zsyscall shims). (Guarded by `NamedNumericSwitchLiteral`
— a mixed named-const + literal switch including a multi-label clause, and an all-literal switch,
output-compared vs Go.)

### A trailing `default` in a switch WITH `fallthrough` is guarded on `!match`
A Go `switch` with no fallthroughs lowers to a plain `if / else if / … / else { default }` chain, where
the trailing `else` correctly runs the default only when no case matched. But a `fallthrough` **breaks
the chain**: the case that `fallthrough` targets is emitted as a SEPARATE, `!match`-guarded `if`
(`if (fallthrough || !match && <labels>) { … }`) so it can be entered both by falling through and by a
direct match. A trailing `default` after such a case was emitted as that `if`'s bare `else` — which
fires whenever the fallthrough-target `if` is false, i.e. **after any matched NON-fallthrough case**, not
only when nothing matched. fmt's `printValue` is exactly this shape:

```go
switch f.Kind() {
case reflect.Int, …: p.fmtInteger(…)          // a matched non-fallthrough case
case reflect.Pointer: … ; fallthrough
case reflect.Chan, reflect.Func, reflect.UnsafePointer: p.fmtPointer(f, verb)
default: p.unknownType(f)                        // wrongly ran after fmtInteger
}
```

so formatting an `int` slice element ran `fmtInteger` AND then `unknownType` (→ reflect name
resolution → a `resolveNameOff` stub → panic), breaking `%v` of every composite. The trailing default
is now emitted `else if (!match) { /* default: */ }` (matchVarName). This is byte-equivalent to the
bare `else` in a pure else-if chain (the default is reached only when `!match` either way) and correct
in the broken chain, so it is a safe general lowering. Like the fallthrough-reached default, the guarded
form leaves C# unable to prove exhaustiveness, so a value-returning terminal switch still gets its
trailing `return default!;`. Guarded by `SwitchFallthroughDefault` (a `fallthrough`+`default` switch
where a matched non-fallthrough case must NOT run the default, output-compared vs Go).

### A NON-TRAILING `default` is guarded on the PRECOMPUTED any-case-match, never the running flag
Go allows `default` in **any** clause position and still picks it only when no case matches — position
is presentation, not semantics. The if-chain lowering emits clauses in **source order**, so a default
that is not last is guarded by a predicate that has only seen the arms emitted *before* it. Every clause
after such a default is then dead code, and the default body runs for values those clauses own. Two
shapes reach the chain, and the corpus held one live instance of each:

**Fallen into.** `encoding/json`'s `decode.go` `array` is the witness:

```go
switch v.Kind() {
case reflect.Interface:
	if v.NumMethod() == 0 { … return nil }
	fallthrough
default:
	d.saveError(&UnmarshalTypeError{Value: "array", Type: v.Type(), …})
	d.skip()
	return nil
case reflect.Array, reflect.Slice:
	break
}
```

The default participates in fallthrough, so it **cannot** be reordered — the `fallthrough` link is
source-order-sensitive. It was emitted `if (fallthrough || !matchᴛ1)`, and for a slice or array target
`matchᴛ1` was still `false` (only the `Interface` arm had run), so the error arm fired and the
`Array, Slice` arm below it was unreachable. `json.Unmarshal` therefore failed **every** JSON array
whose target was not a bare `interface{}` — `json: cannot unmarshal array into Go value of type
[1]interface {}` for a fixed-size array (which is how `net/rpc/jsonrpc` passes its `[1]any` params),
and the identical error for every `[]T`. The default now reads the **precomputed** any-case-match
already built for the mirror shape — the OR of every case condition in the switch, materialized ahead
of the chain — so it fires only when nothing matches:

```csharp
var exprᴛ1 = v.Kind();
var matchᴛ1 = false;
var matchᴛ2 = exprᴛ1 == reflect.ΔInterface || (exprᴛ1 == reflect.Array || exprᴛ1 == reflect.ΔSlice);
if (exprᴛ1 == reflect.ΔInterface) { matchᴛ1 = true; … fallthrough = true; }
if (fallthrough || !matchᴛ2) { /* default: */ … }
if (exprᴛ1 == reflect.Array || exprᴛ1 == reflect.ΔSlice) { matchᴛ1 = true; … }
```

**Participating in no fallthrough at all.** For a non-constant-label tagged switch the converter
already normalized this shape by *moving* the default clause to the end. A **constant**-label switch
that has fallthroughs elsewhere also lowers to the chain, and that reorder gate did not cover it — so
the un-moved default emitted as a **bare block**, with no condition at all, and ran unconditionally.
`internal/bisect`'s `parsePattern` leads with `default: return …parseError`, so every bisect pattern
parse returned "invalid pattern syntax" and all six digit arms below it were dead code.
(`regexp/syntax`'s `parseEscape` has the identical shape and survived only by luck: its default body
re-tests `!isalnum(c)`, which happens to exclude every one of its own case labels.)

These are now guarded **in place** — `if (!matchᴛ2) { /* default: */ … }` — rather than reordered.
Widening the reorder gate was tried and rejected: reordering moves the clause's *statements*, but
comments attach by source position, so `parseEscape`'s default-clause commentary migrated up into the
octal arm above it. Guarding in place fixes the same defect with no code motion, keeps the emitted C#
in Go's clause order, and costs nothing structurally — emitting an `if` instead of a bare block is
precisely what lets the following clause keep its `else`, which is the CS8641 problem the reorder
exists to avoid in the first place.

Guarded by `JsonFixedArrayUnmarshal`, which unmarshals JSON arrays into `[1]any`, `[2]int`, `[3]string`,
nested `[2][2]int` and a struct array — including the over-length (truncate) and under-length
(zero-fill) cases — and, in the other direction, asserts that the `default` arm still produces Go's
exact error for the targets it genuinely owns, so the fix cannot be an over-broad one that drops the
guard.

### A clause's `else` may only be dropped when EVERY preceding clause terminates
In the if-chain lowering the converter omits the `else` before a clause when the preceding clause
ended in a `return` — the chain is then unnecessary, since control cannot reach the later clause
anyway. That decision was driven by a *shallow* "the last statement emitted was a return" flag, which
reports only the **immediately** preceding clause. Dropping the `else` is sound only when **every**
preceding clause terminates.

For a `case` the mistake is invisible: its condition cannot match a value an earlier case already
matched, so the unchained `if` simply evaluates to false. For `default:` it is silently fatal —
`default` has no condition and therefore *always* runs. os's `(*Process).wait` is exactly this shape:

```go
switch s {
case syscall.WAIT_OBJECT_0:      // a bare `break` — falls OUT of the switch
	break
case syscall.WAIT_FAILED:
	return nil, NewSyscallError("WaitForSingleObject", e)
default:
	return nil, errors.New("os: unexpected result from WaitForSingleObject")
}
```

The `break` case is not a Go terminating statement, but the returning `WAIT_FAILED` case set the flag,
so the default emitted as an **unguarded** block that ran straight after the success path:

```csharp
if (exprᴛ2 == syscall.WAIT_OBJECT_0) { do { break; } while (false); }
else if (exprᴛ2 == syscall.WAIT_FAILED) { … return; }
{ /* default: */ … return errors.New("os: unexpected result from WaitForSingleObject"); }
```

Every child-process wait therefore failed — and it compiled cleanly the whole time. The decision now
uses the accumulated `allCasesTerminal` check that the trailing-`return default!;` logic already
relies on (genuine Go terminating-statement analysis, so an `if { return }` with no `else` is
correctly non-terminating). The original condition is kept as one arm of the test, making the change
strictly *add* an `else` where one was missing and never remove one, so no already-correct emission
changes. Guarded by `SwitchFallthroughDefaultReturn`'s `waitShape` (non-constant case labels force the
if-chain; verified to fail without the fix).

### A `break` in a case that also `fallthrough`s must skip the fallthrough
A case body containing a Go `break` is wrapped in `do { … } while (false)` so the `break` has a C#
target in the if-chain lowering (the case above shows the wrapper). A case ending in `fallthrough`
raises a `fallthrough` flag the next clause's guard reads. **A case with both** put the flag *after*
the wrapper:

```csharp
if (exprᴛ1 == stdISO8601ColonTZ || …) { matchᴛ1 = true;
    do {
        if (len(value) >= 1 && value[0] == (rune)'Z') { value = value[1..]; z = ΔUTC; break; }
    } while (false);
    fallthrough = true;                    // ← reached by the break as well
}
```

so the `break` — which in Go exits the **switch** — fell through instead. `time.Parse` is exactly this
shape: RFC3339's `Z07:00` layout element consumes a literal `Z` and breaks, and the fallthrough handed
the remaining text to the numeric-offset arm, which rejected it. Go reports
`extra text: "07:00"`; C# reported `cannot parse "Z07:00" as "Z07:00"`, and every `Z`-terminated
RFC3339 value routed through `Time.UnmarshalText` failed the same way.

Because Go's spec requires `fallthrough` to be the final non-empty statement in its clause, "control
reached the end of the body" *is* "the fallthrough statement was reached" — which is what lets the flag
be raised at the end rather than at the statement. In a break-wrapped case that equivalence holds only
**inside** the wrapper, so the assignment moves there and a `break` skips it:

```csharp
    do {
        if (…) { …; break; }
        fallthrough = true;
    } while (false);
```

A case with a `fallthrough` and no switch-`break` is unaffected (no wrapper exists), as is a case whose
only `break` belongs to a nested loop — `caseBodyHasSwitchBreak` already stops at a nested
loop/switch/select/closure, so no wrapper is emitted and the flag stays where it was. Guarded by
`SwitchBreakBeforeFallthrough` (the `Z`-consuming break-then-fallthrough arm, a fallthrough arm with no
break, a nested-loop `break` that must still fall through, and a break with no fallthrough behind it —
output-compared vs Go).

## Type Switch Statements
For a Go type-switch, C#'s type-pattern `switch` works well. The runtime exposes the dynamic type via `.type()`, and the empty interface is `any`:

```go
func do(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Printf("Twice %v is %v\n", v, v*2)
    case string:
        fmt.Printf("%q is %v bytes long\n", v, len(v))
    default:
        fmt.Printf("I don't know about type %T!\n", v)
    }
}
```

converts to:

```csharp
internal static void @do(any i) {
    switch (i.type()) {
    case nint v: {
        fmt.Printf("Twice %v is %v\n"u8, v, v * 2);
        break;
    }
    case @string v: {
        fmt.Printf("%q is %v bytes long\n"u8, v, len(v));
        break;
    }
    default: {
        var v = i.type();
        fmt.Printf("I don't know about type %T!\n"u8, v);
        break;
    }}
}
```

**Go `int`/`uint` cases and the synthetic concrete case.** A Go `int` maps to C# `nint`, but an `int`-valued *literal* boxed into an interface (`do(1)`) has C# dynamic type `int32`, not `nint`. So a `case int:` emits its native form **plus** a synthetic concrete `case int32:` (and `case uint:` adds `case uint32:`) sharing the same body, to catch both boxings. The exception: if the *same* switch also lists an explicit `case int32:`/`case uint32:` (or `case rune:` ≡ int32), the synthetic is **skipped** — emitting it would duplicate the explicit case (CS8120 "unreachable case") and, being emitted first, would steal the explicit case's values and run the wrong body. With the synthetic suppressed, a typed `int` value (`nint`) hits `case int:` and a typed `int32` value hits `case int32:`, distinctly. (Runtime's `printpanicval` switches over `int, int8, …, int32, …, uint, …, uint32, …`; guarded by the `TypeSwitch` behavioral test.)

**Duplicate-mapped cases — the identical-body merge.** Go type distinctions can *vanish* in the C# type map, making a later case unreachable (CS8120). The canonical example was `uint` + `uintptr` under the old `System.UIntPtr` alias — **now moot: `uintptr` is a distinct golib struct** (see [Constant Values](#constant-values)) and both cases emit their own labels, each dynamic type routing to its own body exactly as in Go. The merge machinery remains for any alias pair that still shares a C# type: a duplicate-mapped case merges **only when its Go body is byte-identical** to the first occurrence's — the earlier label already routes both dynamic types to that shared body, so the merge is exact. A marker comment replaces the duplicate label:

```csharp
case uint64 vΔ1: {
    print(vΔ1);
    break;
}
/* case uintptr vΔ1: merged with an earlier case mapping to the same C# type (identical body) */
case float32 vΔ1: {
```

If the bodies **differ**, both labels are kept and the CS8120 stands: a compile error is preferable to silently routing one Go case's values into another case's body. Duplicate detection keys on the resolved C# type (`uintptr`→`nuint`, `rune`→`int32`, `byte`→`uint8`) per switch statement; the synthetic `int32`/`uint32` cases register too, so an explicit later duplicate of a synthetic with the same body also merges. Guarded by the `TypeSwitch` behavioral test (`uint` + `uintptr` with identical bodies, values hitting both Go paths).

**Multi-type cases stack labels and bind at the tag's interface type.** Go binds a type-switch case
variable at the listed CONCRETE type only when the clause lists exactly **one** type; a multi-type clause
(`case *Alias, *Named:`) binds it at the TAG's static (interface) type. The old emission split such a
clause into one concrete-bound C# case per listed type (duplicating the body), so every body use in an
interface-typed context broke — as an argument (`isGeneric(t)` with `t` a `ж<Alias>`, CS1503), an
interface assignment (CS0266), and an extension-method receiver (CS1929 — 18 errors in go/types alone).
A multi-type clause now emits **stacked labels binding only a discard, over one shared body** and
re-binds the variable to the guard expression — the same re-bind the `default` arm uses — so the body
compiles at the interface type exactly as in Go:

```csharp
switch (x.typ.type()) {
case ж<Alias> _:
case ж<Named> _: {
    var t = x.typ;          // t: Type (the tag's interface type), as in Go
    if (isGeneric(t)) { … }
    break;
}
case ж<ΔSignature> t: {     // single-type case keeps the concrete binding
```

The `_` designation is load-bearing, not stylistic: it forces the label into PATTERN context. A bare
`case int8:` label resolves the identifier as an EXPRESSION first, where `using static go.builtin` finds
the same-named conversion FUNCTION (`int8(…)`) — a method group, neither constant nor type — failing
CS8917 (encoding/binary's `Size`/`intDataSize` stacks; caught by the census build, not the behavioral
corpus, whose labels happened not to collide). `case nil` stacks as `case null:`, a dynamic-interface
label stacks in its non-binding `{} ᴛn when ᴛn._<Iface>(out var _)` form, and the synthetic
`int32`/`uint32` companions of `case int:`/`case uint:` stack with the same discard. The
duplicate-mapped-case merge applies per label (a merged label leaves its marker comment above the stack).
An UNBOUND multi-type clause (`switch x.(type)`) stacks the same way with no re-bind — the body is no
longer duplicated per label. The re-bind re-evaluates the guard EXPRESSION at body entry — harmless
for a pure tag (nothing can mutate it between dispatch and entry), and an IMPURE tag is hoisted into
a one-time temporary first (see *The type-switch tag evaluates exactly once* below). (Guarded by the `TypeSwitchMultiCase` behavioral test — bound multi-type
cases over values and pointers with interface-dispatched body uses, `nil` stacked with a concrete type, an
unbound multi-type clause, and the synthetic-int stacking, output-compared vs Go; also rewrote
`TypeSwitch`'s `case int, int64, uint64:` golden with output proven unchanged.)
**Runtime dispatch — `.type()` unwraps the interface adapters.** The case patterns match against
whatever object the switch operand `.type()` returns, so it must surface the Go DYNAMIC value, not
the C#-only wrapper classes the runtime uses to carry it. A non-empty interface value created from
a Go pointer is a generated `IжAdapter` wrapping the receiver box (`var v shape = &c` emits
`new circleжshape(Ꮡc)`; see [Interfaces](#interfaces)), and an interface-to-interface
assignment can wrap the source in an `IInterfaceAdapter`. `.type()` therefore unwraps —
`IInterfaceAdapter.Value` chains first, then `IжAdapter.Box` — mirroring the type-assert machinery
in `_<T>`, so a Go `case *circle:` (emitted `case ж<circle> t:`) matches the adapter-wrapped value
exactly as it matches the raw box that an EMPTY interface (`any`) holds directly. The bound `t`
IS the original receiver box, so writes through it (`t.Value.r += 10`) alias the original object,
matching Go's interface-holds-the-pointer semantics; `case nil` (emitted `case null:`) still sees
the nil interface unchanged. A known edge remains: an interface holding a **nil `*T`** (in Go a
non-nil interface that matches `case *T:` binding a nil `t`) stays wrapped — no C# type pattern
can bind a null — so it falls to `default` rather than wrongly matching `case null:`. (Guarded by
the `TypeSwitchPointerAdapter` behavioral test — pointer-receiver implementations of a non-empty
interface dispatched through single-type, multi-type, no-bind, and write-through cases, plus
value-receiver, `nil`, and raw-box-in-`any` controls.)

**Both unwrap tiers sit behind ONE `IGoAdapter` probe (2026-07-26).** `.type()` is evaluated once
per type switch, and it used to test `IInterfaceAdapter` and then `IжAdapter` separately — two
*failing* interface type tests for every ordinary Go value, which is what the overwhelming majority
of type switches dispatch on. Measured, that is ~2.9 ns each on the JIT, against a failing sealed-
class test too small to measure: a failing interface `isinst` walks the type's interface map. The
two adapter interfaces now share an empty base marker, `IGoAdapter`, and `.type()` probes it once to
gate both unwraps, dropping the type switch from **16.0 ns to 4.5 ns per iteration** on `PerfIface`.
The unwrap ORDER and results are unchanged; the `string` → `@string` arm moved after the `IжAdapter`
arm, which cannot change an answer because `string` is sealed and implements neither marker. See
[Interfaces](#interfaces) for the same gate on the type-assert side and the full measurement.

**Named-interface case labels dispatch by method set through the adapter registry.** An
INTERFACE-typed case label — named (`case fmt.Stringer:`, `case error:`) or anonymous — must match
by Go METHOD-SET semantics, and after the unwrap above the operand is the raw receiver box, which
never nominally implements a C# interface (the generated pointer adapter does). A plain C# type
pattern (`case Stringer t:`) therefore missed every pointer-sourced implementation. All interface
labels now emit the `when`-guard form the anonymous labels already used —
`case {} Δx when Δx._<Stringer>(out var x):` — routing dispatch through golib's type-assert
machinery, which resolves in order: a **nominal** implementer (value structs made partial, adapter
instances, duck-type wrappers) matches directly; a raw **box** `ж<X>` (or an adapter asserting to a
*different* interface, via its `Box`) re-wraps through `go.AdapterRegistry` — each generated
pointer adapter registers `(typeof(ж<X>), typeof(Iface)) → box => new XжIface(box)` from a
`[ModuleInitializer]`, so the lookup is a dictionary hit and a compiled factory, reflection-free
and Native-AOT-safe (the initializer also roots the adapter against trimming); an *anonymous*
interface falls back to its runtime duck-typing shell (this said "its generated `ᴛAs` duck-typing
conversion" until 2026-07-25, when anonymous interfaces moved onto the same shells named ones use).
The `out var x` binds at
the CASE interface type exactly as Go binds the case variable, the re-wrapped adapter forwards to
the original box (writes through the binding alias the original object), label order is preserved
(C# tests patterns top-to-bottom, and a `when`-guarded pattern never makes a later label
unreachable), and `case nil` is unaffected (`{}` never matches null). The type-assert core is
non-throwing (`TryTypeAssert`), so a non-matching label — the NORMAL control flow in a type
switch — costs no exception; this also makes a nil-interface `v, ok := x.(T)` return `ok=false`
(Go semantics) instead of faulting, and a named interface with no registered adapter is a MISS
rather than the former missing-conversion-method hard error. Known residuals, all of the same shape (the
adapter type does not exist or its module never loaded, so Go would match where C# misses): a
(struct, iface) pair with **no conversion site anywhere** in the program, a **generic** struct's
adapter (an open registration key is unrepresentable and a generic class cannot host a module
initializer), and FOREIGN **value** adapters (`ᴠ`-composed), which are not yet registered.
(Guarded by the `TypeSwitchNamedInterfaceCase` behavioral test — pointer-adapter value, raw
box-in-`any`, value-struct implementer, `case error:` in both adapter-carried and raw-box forms,
non-matching control, label-order precedence both directions, a multi-type clause of two interface
labels, interface-tag-to-interface-label dispatch, and write-through aliasing, output-compared
vs Go.)

## Struct Types
Go structs are converted to C# `struct` types and used on the stack to optimize memory use and reduce GC pressure; when an instance must escape the stack it is wrapped in a heap box, [`ж<T>`](https://github.com/ritchiecarroll/go2cs/blob/master/src/core/golib/%D0%B6.cs) (see [Pointers](#pointers)). Rather than spell out the whole struct body, the converter emits a partial struct carrying a `[GoType]` attribute, and the `TypeGenerator` source generator synthesizes the members (equality, `ISupportMake`, embedding promotion, etc.):

```csharp
[GoType] partial struct Person {
    public @string Name;
    public nint Age;
}
```

The generator also chooses the access modifier from the Go name (exported → `public`, unexported → `internal`), except where the converter emits an explicit modifier — for instance, an unexported type used as the type of an *exported* field is published as `public` to satisfy C# accessibility (the converter emits `public partial struct …` and the generator honors that explicit modifier).

The synthesized value-equality body compares the struct's fields against a parameter named `other` (`public bool Equals(Person other) => this.Name == other.Name && this.Age == other.Age;`). Each comparison's **left operand is qualified with `this.`** so a Go field whose name happens to collide with the parameter — a field literally named `other` — still binds field-to-field. Without the qualification a `type holder struct { mark int; other int }` would emit `other == other.other`, where the left `other` resolves to the *parameter* (a `holder`) rather than the field (an `int`), failing to compile with CS0019 (`==` cannot be applied to `holder` and `int`). `GetHashCode`/`ToString` reference the same field names but have no colliding parameter, so they need no qualification. (Guarded by the `StructFieldNamedOther` behavioral test.)

A combined Go field declaration — `x, y int` — emits a single combined C# line (`internal nint x, y;`) so the output mirrors the Go source's line grouping. The combined form is only used when every name in the group shares the same emitted type and access modifier and none needs per-name special handling; otherwise the converter falls back to one line per name. The fallback applies when any of these hold: a blank field `_` (renamed per occurrence — `_`, `__`, …), a name equal to the enclosing struct type (renamed with the `Δ` collision marker), a per-field array initializer (` = new(N)`), or a mix of exported and unexported names in the same group (`X, y int` → `public nint X;` / `internal nint y;`). Field comments and tags attach to the whole Go field, so they never diverge within a group.

C# does not allow inline or intra-function type definitions, so these are "lifted" out of the function. A **named** local type is lifted with its enclosing function's name as a prefix to avoid collisions — a `type x struct{…}` declared in `main` becomes `main_x`. An **anonymous** struct (or an anonymous struct used as a field/value) is lifted to a synthesized name with a `ᴛ`*N* suffix and marked dynamic, e.g. `[GoType("dyn")] partial struct settingsᴛ1`. Struct "definitions" that match structurally remain usable interchangeably (the generator and implicit conversions handle this). A reference to a lifted type as a bare identifier is renamed to the lifted name, and so is its use as a **slice or array element type** — `[]entry` (where `entry` is a local type) emits `slice<process_entry>`, not the short `slice<entry>` (which is unresolved at package scope → CS0246). The element is resolved through the same lift registry as the bare-identifier and anonymous-struct cases. (Guarded by the `LocalTypeSliceElement` behavioral test, covering both the slice and fixed-array forms; runtime hit this on `printDebugLog`'s `[]readState` and `traceAdvance`'s `[]untracedG`.)

**The `dyn` marker is also the type's RUN-TIME identity, and it must not leak the synthesized name.** An unnamed Go struct has no name to report, so `reflect.Type.String()` and `%T` render it STRUCTURALLY — `struct { X int; y int }`, and `struct {}` for golib's `EmptyStruct`. Because a lift gives the type a synthesized C# name (`settingsᴛ1`), that name would otherwise be what reflection reports; `[GoType("dyn")]` is exactly what distinguishes a lift from an ordinary declared struct, whose Go name IS its own. golib's `GoReflect.TypeNaming` therefore renders a `dyn`-marked value type from the `GoFields` projection — the same field table `NumField`/`Field` and the value side read, so a type's reported name and the fields it hands out cannot disagree — following Go's format exactly: an embedded field contributes its type alone, a tagged field appends the `strconv.Quote`d tag. Before it, `go/ast`'s `TestPrint` reported `ast_internal_test.typeᴛ1` where Go prints `struct { X int; y int }`, and `internal/platform`'s decode error named `[]platform_test.listEntry` rather than Go's structural spelling. (Landed 2026-08-09 with `go/ast`'s 9/9 bank; see [`docs/phase4/DESIGN-reflection-bridge.md`](phase4/DESIGN-reflection-bridge.md).)

A **map whose VALUE type is an anonymous struct** is lifted the same way. A package-level `var m = map[K]struct{…}{…}` — crypto/internal/hpke's `SupportedKEMs` (`map[uint16]struct{ curve ecdh.Curve; hash crypto.Hash; nSecret uint16 }`) and `SupportedAEADs` — names its value struct through the lift so the map type reads `map<uint16, SupportedKEMsᴛ1>`; without it, `getAliasQualifiedTypeName`'s map arm stringified the value as raw Go `struct{…}` syntax straight into the C# map signature (`map<uint16, struct{ curve ecdh.Curve; … }>`) — which C# cannot parse (a CS1519/CS1003 syntax cascade). `extractStructType` already lifts a slice/array **element** struct (its `ArrayType` arm) but has no map arm, so a dedicated `extractMapValueStructType` lifts the map **VALUE** struct at the package-level value-spec composite-literal site. The keyed element literals stay the target-typed `new(…)` constructor form — `[0x0020] = new(ecdh.X25519(), crypto.SHA256, 32)` — which binds to the lifted struct's generated constructor; a func-typed value field (SupportedAEADs' `aead func([]byte) (cipher.AEAD, error)`) lifts to a `Func<…>` field that a method-group or func-value element still fills. Both the declaration type (`getCSharpTypeName` → the map arm) and the literal's own type render (`convMapType` → `getExpressionTypeName`) resolve the value through the shared `liftedTypeMap` (the lift runs before the initializer is converted). (Guarded by the `MapAnonStructValue` behavioral test — a package-level map with an anonymous-struct value type, including a func-typed field, constructed and read back by key, output-compared vs Go.)

The **empty struct `struct{}` is never lifted** — it maps to the shared golib `EmptyStruct`, so a `struct{}{}` composite literal emits `new EmptyStruct()` and a `map[K]struct{}` ("set") emits `map<K, EmptyStruct>`. Lifting an empty struct would be doubly wrong: it has no fields to model, and the lift mis-attributes its name and identity. When the `struct{}{}` is the value assigned to a map element (`seen[k] = struct{}{}`), the enclosing assignment passes the **LHS ident** (`seen`) into the struct-conversion context to name the lift — so the empty struct was being lifted to `<func>_seen` *and registered under `seen`'s own type, the map* `map[K]struct{}`, in the lifted-type registry. That poisoned every later reference to that map type: the function parameter `seen map[K]struct{}` rendered as the phantom struct instead of `map<K, EmptyStruct>`, and its comma-ok deconstruction (`(_, ok) = seen[k]`) and two-arg indexer vanished (CS8130/CS0021), while real-map call sites mismatched (CS1503). `convStructType` now short-circuits an empty struct to `EmptyStruct` before any lift, mirroring the `!isEmptyStruct` guard that `extractStructType` already applies everywhere else. (Guarded by the `EmptyStructMapSet` behavioral test; runtime hit this on `typesEqual`'s `seen map[_typePair]struct{}` parameter.)

An **empty `interface{}` field is never lifted** either — it maps to `any`, exactly as a bare `interface{}` type does. When `visitStructType` lifts an anonymous struct it walks its fields and lifts any *anonymous interface* field to its own named `[GoType("dyn")]` interface. Those three inline lift sites (a plain `interface{}` field, a `*interface{}` field, and a `[]interface{}`/`[N]interface{}` element) type-asserted `*ast.InterfaceType` directly, diverging from `extractInterfaceType` — the canonical lift gate, which already excludes empty interfaces. So encoding/json's slice-encoder cycle memo — `ptr := struct{ ptr interface{}; len int }{v.UnsafePointer(), v.Len()}` — lifted its `ptr interface{}` field to a named empty **marker** interface `encode_ptr_ptr`. A named empty interface is implemented by nothing, so constructing the struct from the boxed `uintptr` failed (`cannot convert from 'uintptr' to '…encode_ptr_ptr'`, CS1503). The three sites now carry the same `!isEmptyInterface` guard, so an empty-interface field falls through to the normal field-type conversion and renders `any` (`*interface{}` → `ж<any>`, `[]interface{}` → `slice<any>`). (Guarded by the `AnonymousStructs` extension `cycleMemo` — an in-function anonymous struct with an `interface{}` field constructed from a pointer, read back through the field, and used as a map key, output-compared vs Go; fails CS1503 without the guard. Part of greening encoding/json.)

**A returned anonymous-struct composite literal records its implicit conversion AFTER it is lifted.** Two structurally-identical anonymous structs are the *same* Go type but the converter lifts each occurrence to a *distinct* C# name, so a conversion between them must be bridged by a recorded `[assembly: GoImplicitConv<…>]` (the `ImplicitConvGenerator` emits the operators). A closure whose **result type** is an anonymous struct that `return`s a **composite literal** of the identical anonymous struct — `mk := func(…) struct{ptr any; len int} { return struct{ptr any; len int}{p, n} }` — lifts the closure-result type (`…_func_R0`) and the composite-literal type (`…_type`) separately, and `visitReturnStmt`'s `checkForDynamicStructs` records the conversion between them. Each side's C# name is resolved through the per-file lifted-type registry (`liftedTypeMap`), but a function-local composite literal is only *added* to that registry **during its own `convExpr`**. The recording therefore had to move to run **after** the result expression is converted: reading the arg's type earlier found it unlifted and stringified it as raw Go `struct{…}` text — an invalid C# generic argument in the emitted attribute (`[assembly: GoImplicitConv<struct{ptr interface{}; len int}, …_func_R0>]`, CS1031 "Type expected"). Recording after the lift resolves both sides to their lifted names (`[assembly: GoImplicitConv<…_type, …_func_R0>]`). The `dynamicCast` template `checkForDynamicStructs` may return is applied to the already-converted result expression identically either way, so the reorder is otherwise output-neutral (the full-stdlib A/B reconvert is byte-identical). This is latent for the current stdlib (encoding/json builds its identical memo struct once into a variable, avoiding a second same-shape lift). (Guarded by the `ClosureReturnAnonStruct` behavioral test.)

### Lifted anonymous structs embedding an interface
archive/tar's ReadFrom-hiding shape — `io.Copy(struct{ io.Writer }{tw}, r)` — exercises four
coupled rules: a SELECTOR embed's interface check resolves the **Sel** (`Writer`), not the
package ident (`io`), so the cross-package interface embed emits as a plain interface FIELD
(the promoted-struct property form made the generator construct the interface — CS0144); the
composite literal routes the element through the interface conversion **at render**
(`interfaceTypes[i]`, not just the record-only call); a receiver placed into an
INTERFACE-typed composite field triggers **direct-ж** (Go's interface holds the `*T`, so the
pointer adapter wraps the box `Ꮡfr`); and the generator emits ONE value-form impl per
(struct, interface) pair, folding a Promoted duplicate in (CS0111). ARGUMENT-position values
of a mismatched delegate type wrap in the named delegate's constructor exactly like
composite-literal fields (generic delegate params stay native — unsubstituted type params
cannot render). Guarded by `AnonymousInterfaces` (`tally`/`fill`, `byteRepeat`, and the
named-array `quad`/`frame` Range slice).

The same interface-field record+route also fires for a **named non-struct** element that
implements the field's interface. The gate above triggered only when the element's underlying is a
struct (or the field is embedded), so a named scalar with a method set — hpack's
`DecodingError{InvalidIndexError(idx)}`, where `type InvalidIndexError int` has an `Error()` method
satisfying the `error` field — recorded no `GoImplement<InvalidIndexError, error>` and passed the
value bare to the interface-typed constructor parameter (which surfaces as `NilType`, CS1503). The
gate now also fires when a named, non-struct, **non-interface** element type `types.Implements` the
field's interface — mirroring the call-argument path, which routes any argument into an interface
parameter with no struct-only restriction. Recording the `GoImplement` is what clears the error (the
generated implementation makes the scalar implement the interface, so the bare pass then converts
implicitly); the render routing (`interfaceTypes[eltIndex]`) is set too, matching the struct case. An
interface-typed element is excluded — it is already the interface and needs no adapter. (Guarded by
the `InterfaceFieldNamedScalar` behavioral test — a named `int` into an `error` field and a named
`string` into a local interface field, positional and keyed forms, output-compared vs Go.)

**Naming a lift that has no name source — `new(struct{ SomeIface })`.** Every dyn-lift derives its
C# type name from context (the declared var, the struct field, the parameter name). A package-level
`var reserved = new(struct{ types.Type })` (go/internal/gccgoimporter's singleton) had NO source:
the initializer is a CallExpr (the composite-literal up-front lift didn't fire), so the declaration
type fell to the raw `t.String()` mangle (`ж<types.Type}>`), and the lift arrived late from the
call-argument path under builtin `new`'s UNNAMED parameter — an EMPTY lift name, declaring
`partial struct  {` and registering `""` for every reference (`@new<>()`,
`[assembly: GoImplement<, …>]`, `new жΔType(…)` — a whole-package syntax cascade). Two-part fix:
`visitValueSpec` lifts a `new(struct{…})` initializer's struct UP FRONT under the var's name
(mirroring the composite-literal and hpke map-value lifts), so the declaration, the `@new<…>` type
argument, the `GoImplement` recordings, and the pointer-adapter names all resolve through
`liftedTypeMap`:
```csharp
[GoType("dyn")] partial struct reservedᴛ1 {
    public global::go.go.types_package.ΔType Type;
}
internal static ж<reservedᴛ1> reserved = @new<reservedᴛ1>();
p.typeList[n] = new reservedᴛ1жΔType(reserved);
```
and `visitStructType` itself falls back to the generic `"type"` when a lift arrives with an empty
name (the FUNCTION-LOCAL `x := new(struct{…})` form still reaches it through the unnamed-parameter
path → `main_type`), so no caller can produce an unnamed type declaration. (Guarded by the
`NewAnonStructIfaceEmbed` behavioral test — the package-level singleton converted to its embedded
interface through the lifted type's pointer adapter, the embedded field filled and called through
the promotion, plus the function-local form — output-compared vs Go.)

### An anonymous struct lifts from ANY depth of its declared type
The lift only happens if the converter can *find* the `struct{…}` literal in the declaration it is
converting, and the probe that found it used to look exactly one level down: through a pointer, or
through a slice/array element, or (a later addition) a map value — never through a composition of
those. So `[]struct{…}` lifted and `[]*struct{…}` did not, and net's

```go
var ipStringTests = []*struct {
	in  IP     // see RFC 791 and RFC 4291
	str string // see RFC 791, RFC 4291 and RFC 5952
	byt []byte
	error
}{ … }
```

emitted the *raw Go type text* into the C# declaration — `slice<ж<struct{in net.IP; str string; byt
[]byte; error}>>` — which is not C# at all: **CS1031 `Type expected`**, followed by a 90-error
syntax cascade that hid every real diagnostic in the file behind it. The shape had been invisible
because a composed occurrence still resolved *if some other declaration in the package happened to
register the identical signature first*; `error` embedded here makes the signature unique, so
nothing did.

The probe is now a recursive descent over the type-composing syntax — pointer, array/slice element,
`...T`, parenthesization, map value then key, channel element — so an anonymous struct (and, by the
same helper, an anonymous interface) is found wherever it sits. The `AnonStructComposedTypes` golden
shows the same shape lifting and its elements constructing normally:

```csharp
[GoType("dyn")] partial struct ptrElemsᴛ1 {
    internal nint @in;
    internal @string str;
    internal error error;
}
internal static slice<ж<ptrElemsᴛ1>> ptrElems = new ж<ptrElemsᴛ1>[]{
    Ꮡ(new ptrElemsᴛ1(1, "one"u8, default!)),
    Ꮡ(new ptrElemsᴛ1(2, "two"u8, default!))
}.slice();
```

This strictly widens what lifts: anything that lifted before still lifts, under the same name. The
walk is deliberately **first-match**, because each lifting caller can name only one anonymous type
per declaration — a type expression carrying two *distinct* anonymous literals (`map[struct{…}]struct{…}`)
lifts the value's and leaves the key's. That residual used to apply to every composed shape; it is
now confined to that one. The map-value case had its own one-off probe, which the recursion subsumes
and which was deleted with it.

Scope, measured by a whole-standard-library A/B reconvert against a converter built from the previous
commit — **not** by a source scan, which got this wrong. A grep for the shape found it only in
`_test.go` files and would have concluded the production corpus was untouched; the A/B found
`encoding/gob/type.cs`, because `bootstrapType("_reserved1", (*struct{ r7 int })(nil))` reaches its
anonymous struct through a *parenthesized pointer conversion* — `(…)` then `*` then the literal — a
composition the scan's pattern never looked for. (Charter §9's false-alarm rule, from the other
direction: a zero-hit scan is only as good as its positive controls, and this one had a control for
`[]*struct{…}` and none for `(*struct{…})`.) The corpus footprint is exactly those seven `tReservedN`
declarations, and the change there is a **naming improvement, not a behavior change**: the lift now
happens at the call argument, where it takes the parameter's name (`eᴛ1`…`eᴛ7`), instead of arriving
late from `convStarExpr`'s fallback as the generic `Δtype`/`Δtypeᴛ1`…. Declarations, uses and the
`package_info.cs` accessibility block all move together. (Guarded by the
`AnonStructComposedTypes` behavioral test: a slice of
pointer-to-anonymous-struct with an embedded `error`, a map to pointer-to-anonymous-struct, and a
slice of slice of anonymous struct, read and written through and output-compared vs Go.)

**The struct-FIELD arm was the same probe, and it now shares the same descent.** `visitStructType`
kept its *own* hand-written peel — a chain of `field.Type.(*ast.StarExpr)` / `.(*ast.StructType)` /
`.(*ast.InterfaceType)` / `.(*ast.ArrayType)` arms, each looking exactly one level down — so a struct
field declared `[N]struct{…}` lifted while `[N]*struct{…}`, `[]*struct{…}`, `map[K]struct{…}` and
`chan struct{…}` fell through to the same raw-Go-text emission. That the map case had already been
patched in as its own arm rather than as a rule is the shape a point-repair leaves behind, and it is
what marked this as the next site. The arm now calls `extractStructType` / `extractInterfaceType`,
the identical helpers every other lift site uses:

```go
type Composed struct {
	Ptrs  [2]*struct{ Size uint32 }
	ByKey map[string]struct{ Count int }
}
```
```csharp
[GoType("dyn")] partial struct Composed_Ptrs  { public uint32 Size; }
[GoType("dyn")] partial struct Composed_ByKey { public nint Count; }

[GoType] partial struct Composed {                     // package_info.cs records [GoValueClone("Ptrs")]
    public array<ж<Composed_Ptrs>> Ptrs = new(2);
    public map<@string, Composed_ByKey> ByKey;          // was: map<@string, struct{Count int}>
}
```

Two properties keep the shared helper faithful to what the arm did before. The lift name stays
`<struct>_<field>`, which is well-defined for every shape because a field type carrying an anonymous
literal always *names* the field — the Go spec makes an embedded field a type name, never a literal.
And **sub-struct tracking** (`subStructTypes`, which feeds `addImplicitSubStructConversions`) still
records only the two shapes it ever recorded — the field *is* the anonymous struct, or a pointer
straight to it — because that map describes the field's own declared type; a struct reached through a
slice/array/map/channel element is not the field's type and never was tracked.

Measured by the same whole-standard-library A/B, the widening has **no corpus consumer today**: no
converted package declares a composed anonymous-struct field. What the A/B *did* change is four files
in two packages, all one incidental canonicalization — the shared helpers exclude the **empty**
`struct{}`/`interface{}`, and the old field arm did not:

```csharp
- [GoType("dyn")] partial struct Func_opaque { }          // …and NamedArg__NamedFieldsRequired, Out__…
- [GoType] partial struct Func { internal Func_opaque opaque; }
+ [GoType] partial struct Func { internal EmptyStruct opaque; // unexported field to disallow conversions
```

Go's `opaque struct{}` is `struct{}`, and golib's `EmptyStruct` is what every other site already maps
it to — so `runtime.Func`, `database/sql`'s `NamedArg` and `Out` stop minting a private empty type
apiece, three `[GoType("dyn")]` declarations and their `package_info.cs` entries disappear, and the Go
trailing comment lands back where Go writes it. Nothing referenced the removed names (verified across
the whole reconverted corpus, with the baseline emission as the positive control), and the 302-package
corpus builds with 0 errors. (Guarded by the `AnonStructArrayElement` behavioral test, extended: a
`[2]*struct{…}` field, a `[]*struct{…}` field, a `map[K]struct{…}` field and a `map[K]interface{…}`
field, each read back through its lifted type, alongside the pre-existing one-level `[N]struct{…}`
control and the parenthesized `(*struct{ r7 int })(nil)` conversion. Its A/B reproduces the defect
directly: against the previous binary the three struct fields emit raw `struct{…}` text. The composed
fields are read at their ZERO values on purpose — *constructing* a value of an anonymous struct type
lifts a second, function-scoped name for the same Go type, and a container of it has no implicit
conversion to bridge the two. That is the recorded cross-context anonymous-lift identity split, which
applies equally to the one-level shape and is a separate increment.)

### A global addressed only by the package's own `_test.go` is still heap-boxed
A Go pointer to a package-level var aliases that var's real storage, which in C# means the global
must be backed by a heap box (see [Pointers](#pointers)); `packageAddressedGlobals` decides that by
scanning the package for `&g`. But `go/packages` excludes `_test.go` from a production package, so
an address taken *only* by the package's own in-package test half is invisible at the declaration.
path/filepath is the canonical case — `path.go` declares `var lstat = os.Lstat // for testing` and
`export_test.go` declares `var LstatP = &lstat`, the whole point being that a test can swap the
implementation the production `Walk` calls. The production emission left `lstat` a plain field, and
the test variant's `Ꮡlstat` named a box nothing declared: **CS0103**.

The converter now scans the build-selected in-package `_test.go` files for the identifiers they take
the address of and folds them into the addressed-global set, so the production declaration carries
the box:

```csharp
internal static ж<Func<@string, (fs.FileInfo, error)>> Ꮡlstat = new(os.Lstat);
internal static ref Func<@string, (fs.FileInfo, error)> lstat => ref Ꮡlstat.ValueSlot;  // for testing
```

Three properties make this the right shape rather than a `-tests`-only patch:

- **It runs in ordinary conversion too**, exactly as `siblingTestFuncMethodNames` does for reference
  spelling, so a package's production storage shape is **mode-stable** — an `-stdlib` reconvert and a
  `-tests` run emit the same bytes. Conditioning it on `-tests` would make the banked corpus flip
  between the two.
- **The scan is a cheap direct directory read, not a second type-check** — no test dependency graph is
  loaded. It is therefore name-based, and the production pass resolves each candidate against the real
  package scope, dropping anything that is not a package-level var (a type, a func, an import
  qualifier, a name that exists only in the test file).
- **It errs toward recording nothing.** Names bound anywhere inside the enclosing top-level
  declaration — receiver, parameters, results, `:=`, `var`/`const`/`type`, range and type-switch
  bindings — are excluded, so `&counter` on a local that shadows a global does not box the global.
  Under-recording restores today's loud CS0103; over-recording would silently box a global no pointer
  aliases.

Only **build-selected** test files are scanned (`go/build`'s `MatchFile`, with the run's `GOOS`/
`GOARCH` and `-tags`), so the boxed set is a property of the build configuration exactly as the
converted production sources themselves are: `path_windows_test.go` contributes on Windows and
`path_unix_test.go` does not. That is the same rule `siblingTestFuncMethodNames` already follows, and
it is the correct answer — a global no *selected* file addresses needs no box in that configuration.

Measured across the whole standard library by an A/B reconvert: **13 globals in 13 files**, and every
single one is a Go *"for testing"* hook — `path/filepath` and `os`'s `lstat`, `os`'s
`testingForceReadDirLstat` and `allowReadDirFileID`, `runtime`'s `readRandomFailed`, `useAeshash`,
`doubleCheckReadMemStats`, `casgstatusAlwaysTrack`, `forcegcperiod` and `timeBeginPeriodRetValue`,
`reflect`'s `callGC` (whose own comment reads *"for testing; see TestCallMethodJump and
TestCallArgLive"*), `internal/poll`'s `logInitFD`, `net/http`'s `maxWriteWaitBeforeConnReuse` and
`testHookEnterRoundTrip`, and `time`'s `usPacific`. No false positives, which is what the
bind-aware exclusion buys — and the same set is forward work, since `os`, `runtime`, `reflect`,
`net/http`, `internal/poll` and `time` all need those hooks to alias real storage before their own
suites can pass.

External (`package foo_test`) test files are deliberately not scanned: they reach the package only
through its exported surface, and `&otherpkg.Var` from *any* other package is a separate, still-open
gap — `collectAddressedGlobals` only ever scans the package under conversion. (Guarded by the
`SiblingTestAddressedGlobal` behavioral test, whose `export_test.go` addresses a bare global, a
global through a field selector, and a global from a function body, against negatives for a
test-file-local declarator and a shadowing local. It is the first behavioral project to carry a
`_test.go`; the corpus harness skips `_test.go` when pairing sources with `.cs` goldens, since a
production transpile never emits one.)

### Astral rune literals
A quoted rune literal beyond the BMP (`'\U0001D504'`) cannot be a C# char literal — it emits
the code point (`(rune)0x1D504`); BMP literals keep their source text verbatim (html's entity
table, CS1012 ×133). Guarded by `StringConvPostfix` (`glyphs`).

### Type-switch default arm binds the interface value
The default clause binds the guard to the ORIGINAL guarded expression (`var x = err;`), whose
static type is the interface — the switch-operand form (`err.type()`) is object and cannot
flow back out (`default: return x`, go/build/constraint's pushNot, CS0266).

### The type-switch tag evaluates exactly once
Go evaluates the TypeSwitchGuard's operand exactly once, but the default-arm and multi-type
re-binds above textually re-emit the tag expression, so a tag containing a **call or channel
receive** evaluated once at dispatch and again at each matched re-bind arm —
`switch p := recover().(type)` re-called `recover()` (which returns nil the second time,
silently losing the recovered value in a `case nil, *bailout:`-style arm that reads `p`;
go/types handleBailout), and a `switch v := (<-ch).(type)` re-received. Such a tag is now
HOISTED into a one-time temporary, and both the dispatch operand and every re-bind read it:

```csharp
var switchᴛ1 = next(x);
switch (switchᴛ1.type()) {
case @string _:
case bool _: {
    var v = switchᴛ1;      // re-bind reads the temp — next() ran exactly once
    …
default: {
    var v = switchᴛ1;
```

The hoist is deliberately GATED — only a tag containing a call (conversions hoist
conservatively; the temp is merely unneeded) or a receive, and only when some arm actually
re-binds (a bound default, or a multi-type clause with a non-blank ident) — so every pure-tag
type switch keeps its direct, byte-identical emission. The temp name comes from the
per-package `getGlobalTempVarName` counter (`switchᴛN`), so nested and sibling hoists never
collide. Single-type concrete labels and the `when`-guard interface labels bind from the
dispatch operand's pattern variable and never re-evaluate the tag regardless. (Guarded by the
`TypeSwitchImpureTag` behavioral test — a counting-function tag whose per-switch eval count is
printed and output-compared vs Go [the pre-fix emission provably prints `calls: 7` for Go's
`calls: 4`], a `recover()` tag in a deferred multi-type switch, and a channel-receive tag that
would deadlock on re-receive.)

### Generated code global::-qualifies root-namespace references
Inside a package whose namespace nests a same-named segment (go/build/constraint emits into
`namespace go.go.build`), C# binds a generated reference's leading `go` RELATIVELY to `go.go`
(CS0234). The generators qualify every type-reference position via `GlobalQualify`
(Common.cs); generated signatures also carry parameter REF KINDS (`in slice<byte>`) and a
canned `System.IFormattable` impl where the interface inherits it (the hand-finished io
stub's dyn machinery).

The **converter** faces the same `go.go` shadowing in the import `using` directives it emits for a
`go/*` package (go/token lands in `namespace go.go`, imports `sync`/`unicode` sub-namespaces): a
rooted `using atomic = go.sync.atomic_package;` / `using go.sync;` binds its leading `go` to the
enclosing `go.go` namespace, resolving `go.sync` to the nonexistent `go.go.sync` (CS0234). `rootQualifyIfAmbiguous` routes its rooting returns through `rootQualified`, which emits `global::go.`
instead of a bare `go.` when the package's namespace second segment is itself `go`:

```csharp
using atomic = global::go.sync.atomic_package;
using global::go.sync;
```

The shadowing is NOT limited to `go/*` packages themselves: any package with a `go/*` package
anywhere in its transitive import CLOSURE compiles with `namespace go.go` in scope (its referenced
assembly makes `go.go` a member of namespace `go`), and C#'s inner-to-outer lookup then binds the
bare leading `go` of a rooted using target to that member from EVERY namespace nested under the
root — internal/fuzz (imports go/ast) emitted `using bits = go.math.bits_package;` inside
`namespace go.@internal`, resolving to the nonexistent `go.go.math` (CS0234 ×16, plus the same
shape in net/rpc's `Δhttp` alias and testing/internal/testdeps). `rootQualified` therefore also
emits `global::go.` when `packageChildNamespaces` carries the `go.go` key (populated from the
transitive import closure by `computeImportAliasRenames`' pre-pass). A package with no `go/*`
anywhere in its closure — every package that was compiling before, and all pre-existing behavioral
tests — keeps the bare `go.` prefix, so there is no golden churn. Cleared go/token, go/doc/comment,
go/build/constraint (own-namespace branch); internal/fuzz's 18 CS0234 and net/rpc's latent pair
(closure branch). Guarded by the `GoNamespaceShadow` behavioral test, which covers BOTH branches
through a nested local module literally named `go/nsshadow` (emitting `namespace go.go`, the shape
a single-file behavioral test cannot express): the nested lib imports `math` + `math/rand` so its
own rooted using exercises the own-namespace branch, and the importing `main` package (namespace
`go`, with `go.go` in its closure) exercises the closure branch.

**Under `-tests` the shadow gate spans BOTH compilation halves, and the directly-composed using
targets must go through it too.** The gate had two holes that only a test conversion can expose,
and math/rand/v2 (whose `regress_test.go` imports `go/format`) hit both — 13 of the package's 22
compile errors:

1. *The closure was computed per PACKAGE, not per ASSEMBLY.* A `-tests` run recompiles the
   package's PRODUCTION sources into the test assembly, so that assembly's reference closure is the
   UNION of the production and `_test.go` closures. The production conversion pass saw only its own
   half, never learned `go.go` was in scope, and emitted bare `using bits = go.math.bits_package;`
   into a compilation that did contain `go.go`. `collectSiblingTestClosure` now runs a
   metadata-only (`NeedName|NeedImports|NeedDeps`) load of the test variants before the production
   conversion and records their transitive import paths in `siblingClosureImportPaths`, which
   `computeImportAliasRenames` folds into the closure it walks — so every consumer of the namespace
   maps (the shadow gate, `rootQualifyIfAmbiguous`, `isStrippedGoPathPackageRef`) describes the
   assembly rather than the package. The set is empty for every non-`-tests` conversion, so no
   other output moves.
2. *Targets composed straight from `packageNamespace` bypassed `rootQualified` entirely.* Both the
   package-under-test anchor (`visitImportSpec`'s `isPackageUnderTest` branch, which REPLACES the
   `rootQualifyIfAmbiguous`-derived target with `<packageNamespace>.<pkg>_package`) and the test
   host's `using go.testing_runtime;` were bare, which is why one emitted file could show a
   correctly-qualified `using iotest = global::go.testing.iotest_package;` beside a broken
   `using static go.math.rand.rand_package;`. `globalQualifyRooted` applies the same gate to an
   ALREADY-rooted path and both sites now route through it. It is idempotent and a no-op with no
   shadow, so unshadowed packages emit byte-identically.

Both holes fire for ANY package whose test closure reaches a `go/*` package, and a `regress_test.go`
importing `go/format` is a common stdlib idiom — this is not a v2 quirk. Guarded by
`TestGlobalQualifyRootedForcesGlobalUnderRootShadow` and `TestSiblingClosureContributesRootShadow`
(`src/go2cs/rootShadowQualification_test.go`); the behavioral corpus cannot cover them because it
never runs `-tests` and no behavioral package imports a `go/*` package.

### A GoImplement record's adapter key is canonical, not textual
`interfaceImplementations` is keyed by RENDERED type name, so one resolved pair recorded under two
spellings is two records — and go2cs-gen turns two records into two definitions of the SAME adapter
type. The interface side arrives class-relative when PARSED from a `package_info.cs`
(`rand_package.Source`, via `loadPackageImplements`) and fully namespace-qualified when rendered at
a CAST SITE (`go.math.rand.rand_package.Source`). `canonicalRecordIfaceName` stripped only the root
prefix, so the two keyed differently, the foreign-adapter existence proof missed, and the pair was
re-recorded under the second spelling.

Under `-tests` this is routine rather than exotic: the EXTERNAL (`package <name>_test`) variant
reaches the package under test through its import path, so it renders that package's types
qualified, while the seeded production metadata carries them short. math/rand/v2 emitted both
`[assembly: GoImplement<PCG, Source>(Pointer = true)]` and
`[assembly: GoImplement<go.math.rand.rand_package.PCG, go.math.rand.rand_package.Source>(Pointer = true)]`,
and `ImplementGenerator`'s `GetUniqueHintName` silently uniquified the duplicate FILE name — so the
duplicate TYPE reached the compiler as CS0102 + CS0111 ×5 + CS8646 on `rand_package.PCGжSource`.
(`math/rand` escapes only by luck: its one self-qualified record targets a different interface than
any short record.)

The adapter's identity is exactly `<class>_package.<Type>` — the pair `ImplementGenerator` composes
its class name from — so the record key collapses a longer chain to its `<pkg>_package` tail, leaving
a nested type reference (`x.y_package.Outer.Inner`) untouched. The EMISSION side is normalized to
match: `stripLocalTypeQualifier` rewrites a reference naming one of THIS package's own types through
the package's fully-qualified class back to the bare local form the attribute file's
`using static <ns>.<pkg>_package;` resolves, so the two spellings collapse in the emitting
`HashSet`. Guarded by `TestStripLocalTypeQualifier`.

*(Superseded in the details, 2026-08-02: `canonicalRecordIfaceName` is retired. Both record sets now
compose one key through `implementRecordKey` / `canonicalImplementRecordIfaceName` — same collapse
rule, now shared rather than duplicated. See* A foreign implement record is keyed in ONE spelling,
and a VALUE one is trusted only for a partial struct.*)*

⚠ **The collapse only reaches records the CURRENT run rendered — a stale spelling already on disk
slips past it, because `package_info_external_test.cs` / `package_test_info.cs` are MERGE-PRESERVING** (see
the anchor-routing note above). The merge reads each existing attribute line VERBATIM into the
emitting `HashSet`, so a record persisted by an OLDER converter — before `stripLocalTypeQualifier`
reduced it — arrives under the pre-collapse spelling and never meets the fresh, already-collapsed
one. `container/heap` (banked at package #8, before the collapse landed) committed
`[assembly: GoImplement<IntHeap, go.container.heap_package.Interface>(Pointer = true)]`; a fresh
`-tests` run of a NESTED package-under-test now renders that same pair as the bare
`[assembly: GoImplement<IntHeap, Interface>(Pointer = true)]` (the qualified `go.container.heap_package.`
prefix gets `rootQualifySubNamespaceTypeRefs`-rooted then stripped, whereas a TOP-LEVEL package's
`sort_package.Interface` is never rooted so it is never stripped and stays byte-stable). The two
spellings both survived the merge → `GetUniqueHintName` uniquified the second `.g.cs` → a duplicate
`IntHeapжInterface` reached the compiler (CS0102 + CS0111 + CS8646). `writePackageInfoFile` now runs
every merged-in `[assembly: GoImplement<…>]` line through the SAME `qualifyLocalTypeRef` pipeline the
fresh render applies, so a stale record collapses into the canonical one instead of duplicating it —
the whole-line pass is safe because the pipeline only rewrites package-qualified name tokens (bare
flag keywords `Pointer`/`Promoted` and the `assembly`/`GoImplement` scaffolding are untouched), and it
is scoped to `GoImplement` lines specifically so it cannot rewrite a `GoImplicitConv` attribute's
`ValueType = "…"` keyword (`ValueType` is a System-colliding name the rooter would otherwise qualify).
Because whole-package conversions (`-stdlib`, every behavioral test) write with `mergeExisting=false`
they never take this path, so the corpus and behavioral goldens are byte-identical. Guarded by
`TestMergedStaleGoImplementSpellingCollapses`.

### A test project's references cover UNROOTED alias targets (single- AND multi-segment)
A `-tests` project sets `DisableTransitiveProjectReferences`, so its references are the
direct-import closure plus whatever `aliasReferenceImports` recovers by scanning the emitted `using`
aliases for namespace tokens. The scan matched only the ROOTED token (`go.hash_package`), but a
SINGLE-SEGMENT package emits its alias UNROOTED — `using hash = hash_package;` inside
`namespace go.math.rand`, where C#'s outward lookup finds the class in the enclosing root namespace
with no qualifier. math/rand/v2's `chacha8_test.cs` needs `hash` purely because `sha256.New()`
RETURNS `hash.Hash`, so the package appears in no import list and only this scan could have found
it: the reference went missing and the build failed CS0246 on `hash_package`. The scan now also
carries a bare token per single-segment package, matched on a SEGMENT boundary (`target == token` or
`target` starts with `token + "."`) — a substring test would let `hash_package` match
`go.hash.maphash_package` and pull in a package nothing references. Guarded by
`TestAliasReferenceImportsMatchesUnrootedSingleSegmentAlias` and
`TestAliasReferenceImportsDoesNotMatchAcrossSegmentBoundaries`.

A **MULTI-segment** package hits the identical gap when the test's enclosing namespace SHADOWS the
root `go`. From `namespace go.math` the alias for os/exec is emitted ROOTED — `using exec =
go.os.exec_package;` (math/rand's `default_test.cs`, caught by the `HasSuffix(target, token)` arm) —
but from a namespace whose first segment re-binds `go`, the alias is emitted UNROOTED and relies on
C# outward lookup: go/doc/comment's `std_test.cs` (in `namespace go.go.doc`) and internal/abi's
`abi_test.cs` (in `namespace go.@internal`) both emit `using exec = os.exec_package;`, again purely
because `testenv.Command(…)` RETURNS `*exec.Cmd` so os/exec appears in no import list. The rooted
token (`go.os.exec_package`) is now ALSO matched when it ends with the unrooted target after a
segment boundary — `HasSuffix(token, "." + target)`, so `os.exec_package` matches
`go.os.exec_package` while the leading `.` anchor keeps `os.exec_package` from matching an unrelated
`go.notos.exec_package`. This was the single shared root cause blocking both internal/abi and
go/doc/comment (CS0246 on the `os` namespace). Guarded by
`TestAliasReferenceImportsMatchesUnrootedMultiSegmentAlias` and
`TestAliasReferenceImportsUnrootedTailAnchoredOnSegmentBoundary`.

**An emitted CONVERSION RECORD names packages no import list and no alias mentions.** A `using` alias
is not the only line in the test metadata that must BIND: go2cs-gen realizes every
`[assembly: GoImplement<…>]` / `[assembly: GoImplicitConv<…>]` record into a generated adapter,
partial or operator, so both generic arguments have to resolve at the attribute itself. The converter
records an interface pair from a type's *use*, and that use can be entirely implicit —
os/signal's test does `cmd.Stdout = &buf`, whose os/exec field type is `io.Writer`, so
`package_test_info.cs` carries

```csharp
[assembly: GoImplement<strings_package.Builder, io_package.Writer>(Pointer = true)]
```

while `io` appears in no import list of the production package or its tests, and in no alias. Under
`DisableTransitiveProjectReferences` that is CS0246 on `io_package` at the attribute line, plus a
cascading go2cs-gen **CS8785** (`ImplementGenerator failed … second generic type argument must be an
interface`) once the unbound interface degrades to an error type — the generator then contributes
nothing and the whole package's adapters vanish. The scan therefore also reads the record lines,
extracting each type reference's **package-class qualifier** — everything up to and including the
first segment ending in `_package` (`io_package`, `go.io.fs_package`, `go.@internal.abi_package`).
That is deliberately the qualifier, not the whole type reference: it has exactly the shape a `using`
alias TARGET has, so the *same* three token-match arms above decide both, with no second matcher to
keep in sync. Only the record's generic argument list is scanned (first `<` to last `>`, so a nested
`ж<…>` argument is covered whole) — an attribute's `(Pointer = true)` / `(ValueType = "…")` payload is
metadata, and the `ValueType` is a string, not a reference. Additive as before, and the manifest's
dependency list stays import-derived. This is what lets **os/signal** validate (its `TestCtrlBreak`,
1/1 vs `go test`). Guarded by `TestAliasReferenceImportsMatchesConversionRecordPackages`,
`TestAliasReferenceImportsMatchesConversionRecordQualifierShapes` and
`TestAliasReferenceImportsIgnoresConversionRecordAttributePayload`.

**Referencing a `go/*`-package TYPE loses a root segment because the path's own `go` collides with the root namespace.** A `go/ast` type reference renders correctly as `go.go.ast_package.X` (root `go` + the path's `go.ast` → namespace `go.go`, class `ast_package`), but `convertToCSTypeName` then strips the *leading* `go.` as a redundant root (bodies live inside `namespace go`), leaving `go.ast_package.X` — namespace `go`, which has no `ast_package` (CS0234/CS0426 in the go/* consumers go/doc, go/printer, go/internal/typeparams, whose GoImplement attributes and `using` aliases both carry the stripped form). The two rooting helpers now recognise this: `isStrippedGoPathPackageRef` splits the ref at its first `_package` class segment and tests the *namespace* portion against `packageChildNamespaces` (the current package's rooted import-closure namespaces): the ref is stripped iff that namespace is NOT already a real rooted namespace but *becomes* one when the root `go.` is prepended. This is a **membership** test, not a string-shape test, so it recognises a stripped go/*-package ref at any depth — `go.ast_package` (ns `go`✗ → `go.go`✓), `go.build.constraint_package` (ns `go.build`✗ → `go.go.build`✓, three-segment `go/build/constraint`), `go.doc.comment_package` (ns `go.doc`✗ → `go.go.doc`✓) — while leaving a genuinely-rooted ref alone (`go.io.fs_package` — ns `go.io` is already real). (The earlier two-segment string heuristic — "the class segment sits immediately after `go.`" — recognised only the depth-one `go.ast_package` shape and silently missed the three-segment `go/build/constraint` and `go/doc/comment` sub-package refs, which are string-indistinguishable from a correctly-rooted `go.io.fs_package`; the membership test is what disambiguates them.) `rootQualifySubNamespaceTypeRefs` (the assembly-scope GoImplement/GoImplicitConv attributes) re-roots the stripped form to a bare `go.go.ast_package`; `rootQualifyIfAmbiguous` (the in-namespace `using` aliases) re-roots to `global::go.go.ast_package` — always `global::`, because a bare `go.go.<pkg>_package` re-binds its leading `go` to the nearest enclosing `go` from *any* importer (a go/*-package's own `go.go.*` namespace, and equally `internal/pkgbits` at `go.internal.pkgbits` resolving the second `go` inside `go.go`, CS0234). This un-blocks the whole go/* chain at the rooting level (go/doc's own-errors 17 → 1); each go/* package still needs its remaining per-package residuals (e.g. a methodless-func-type's `[GoTypeAlias]` still names an inline-rendered `ΔFilter`) to fully compile. The depth-one shape is now guarded by `GoNamespaceShadow` (its `go/nsshadow` nested module's import renders through `isStrippedGoPathPackageRef` → `using nsshadow = global::go.go.nsshadow_package;`); the multi-segment sub-package depth (`go/build/constraint`) remains census-verified only — the A/B reconvert-diff showed only the four `go/build/constraint`- and `go/doc/comment`-importing packages, go/build, go/doc, go/parser, go/printer, gaining the corrected double-`go` rooting, with the depth-one `go.go.ast_package` refs unchanged and zero collateral.

**BCL names in generator templates are global::-qualified too — a Go type can shadow any bare BCL
name.** The generated partials sit inside the package class, where every Go type in the package is
a sibling member that wins name lookup over `System.*`: internal/trace/traceviewer declares
`type Range struct`, so the named-string wrapper's sub-slice indexer `this[Range range]` bound the
Go `Range` instead of `System.Range` (CS1503 inside its own `ViewType.g.cs`). This is a *class* of
collisions, not one bug — any package declaring a type named `Range`, `Index`, `Type`, `Span`, … is
exposed — so the audit qualified every BCL reference the TypeGenerator templates emit:
`global::System.Range` (string/slice/array indexers), `global::System.Span<T>`/`ReadOnlySpan<byte>`,
the `IEnumerator`/`IEnumerable` members, `ICloneable`, `IEquatable` and the `System.Numerics`
operator interfaces on numeric wrappers, `System.Type`/`Reflection.MethodInfo`/`Activator`/
`NotImplementedException`/`[DebuggerNonUserCode]` in the dynamic-interface machinery, and the
`GeneratedCode` attribute stamped on every generated declaration (`Common.cs`, shared by all
generators). golib names (`slice<T>`, `NilType`, `IChannel`, …) stay bare — they live in the `go`
namespace the generated code owns. Converter-emitted visible code is not part of this rule (it
renders BCL names by the file-scoped conventions above). (Guarded by `BclTypeNameShadow` —
a package declaring `type Range struct` alongside a named string type and a named slice type, both
sub-sliced with the Go `Range`'s fields as bounds, output vs Go.)

### Generic embedded fields
A GENERIC embed (`entry[K,V]` embedding `node[K,V]`, internal/concurrent) arrives in the AST as
an `IndexExpr`/`IndexListExpr` over the base type; the anonymous-field walk unwraps it (plain,
pointer, and selector forms) and the member emits under the **base name** with type arguments
stripped **before** the selector dot-strip — the arguments may contain qualified types whose
dots otherwise win the LastIndex (`*concurrent.HashTrieMap[T, weak.Pointer[T]]` misnamed its
member `Pointer` instead of `HashTrieMap`). The TypeGenerator's promoted accessors carry the
type parameters on the instance param (`ref Δentry<K, V> instance`) and strip them from the
member access (`instance.node.isEntry`). A promoted method call through a raw ж **box local**
hops `X.Value` ahead of the cross-package pointer-embed hop
(`m.Value.HashTrieMap.Value.Load(value)`, unique). BANKED: unqualified promoted METHOD calls
through a generic embed (`w.show()`) — receiver wrappers resolve the embedded type by exact
name; qualified calls work. Guarded by `GenericStructFields` (`wrapped[T]`/`tag[T]`) and
`CrossPkgUser` (`holder[T]` embedding `*CrossPkgLib.Cache[T]`).

### A func literal in an `any` slot states its Go result type explicitly
A function literal converted into a real **empty-interface** parameter has no delegate target
type, so C# natural-types it from its return arms — `func(x int) int { return 0 }` inferred
`Func<nint, int>` (the literal `0` is C# `int`, i.e. Go `int32`), and the natural type becomes
the value's runtime dynamic type, which reflection then classifies: `func(int) int` and
`func(int) int32` collapsed to ONE managed type, so `quick.CheckEqual` saw equal func types
where Go's differ (testing/quick's TestFailure #3). The emission states the declared Go result
type explicitly:
```go
CheckEqual(func(x int) int { return 0 }, func(x int) int32 { return 0 }, nil)
```
```csharp
CheckEqual(nint (nint x) => 0, int (nint x) => 0, default!);
```
Scoped to single-result literals in `any` slots (`CallExprContext.emptyInterfaceArgs` →
`LambdaContext.untypedInterfaceTarget` → convFuncLit's explicit-return-type mechanism);
target-typed positions are untouched — their delegate supplies the type, and an explicit return
type there could only add identity-match constraints against hand-written stub delegate types.
Multi-result `any`-slot literals keep natural tuple typing (no demonstrated consumer). Guarded
by the `LiftedLocalTypes` behavioral test; operationally by testing/quick's banked suite.

### Lifted function-local types: anonymous structs dedupe, named types carry [GoLocalName]
C# forbids type declarations in method bodies, so the converter lifts function-local types to
package scope under a function-prefixed name. Two Go type-identity rules ride the lift:

- **Structurally identical anonymous struct types are ONE Go type.** Repeated textual
  occurrences (`new(struct{ A Struct })` four times in encoding/binary's TestSizeStructCache)
  must lift to a SINGLE C# type — per-occurrence lifts split `reflect.Type` identity per
  occurrence, so binary's `structSize` cache gained four entries where Go adds one. Lifted
  anonymous structs dedupe by structural signature within a function; NAMED local declarations
  keep per-declaration identity and never dedupe. (The cross-function/package-level anonymous
  split is a recorded residual.)
- **A lifted local NAMED type carries its original Go name** via the golib `[GoLocalName]`
  attribute — a SEPARATE attribute, never a `[GoType]` definition token (the TypeGenerator
  matches that slot by exact string and throws on unknown forms). The reflection bridge's
  naming (`GoReflect.GoQualifiedName` → `Type.String()`, `%T`) prefers it, so a local type
  prints Go's `*binary.Person`, never the lifted `*binary.TestNoFixedSize_Person`
  (TestNoFixedSize asserts the exact error text). Being read off the runtime `Type` is also what
  makes the stamp movable, so it is written on the `package_info.cs` accessibility record and the
  lifted declaration reads as the plain lift it is
  ([Extended attributes](#extended-attributes-what-stays-on-the-declaration-and-what-moves)):
```go
func TestNoFixedSize(t *testing.T) {
	type Person struct { … }
```
```csharp
[GoType("dyn")] partial struct TestNoFixedSize_Person {

// package_info.cs
[GoLocalName("Person")] public partial struct TestNoFixedSize_Person {}
```
Guarded by the `LiftedLocalTypes` behavioral test (single lifted declaration for repeated
anonymous occurrences + `[GoLocalName]` pinned in the golden); operationally by
encoding/binary's banked suite.

### A methodless named func type renders as its base delegate

Go treats a named func type as freely interconvertible with its underlying `func(...)` when the
type has **no methods** — the name is purely documentary. `type releaseConn func(error)`
(database/sql) and `type CancelFunc func()` (context) are assigned to and from anonymous
`func(...)` values without conversion: `grabConn` returns `releaseConn`, `queryDC` takes
`func(error)`, and Go passes one to the other. Emitting the named type as a *distinct* C#
delegate (`ΔreleaseConn`) broke this — the base `Action<error>` its underlying renders to has no
implicit conversion to it (CS1503/CS0029), and the mismatch even excluded the `ж`-receiver
overload of methods taking such a param, so `db.pingDC(...)` on a boxed `*DB` failed with CS1929.

A **non-generic** named func type with **no methods** is therefore rendered AS its base C#
delegate (`Action`/`Func<…>`) everywhere it is referenced (`getAliasQualifiedTypeName`/`getFullyQualifiedTypeName` return
the underlying signature), and its declaration is skipped (`visitFuncType` emits only a marker
comment). Every named↔underlying conversion becomes identity, exactly as Go models it:
```csharp
// type releaseConn is a methodless func type — rendered inline as its base delegate
internal static (ж<driverConn>, Action<error>, error) grabConn(this ж<ΔConn> Ꮡc, context.Context _) { … }
internal static error queryDC(this ref DB db, …, Action<error> release, …) { … }
```
Three exclusions keep the collapse sound — a type is left as a named delegate if any holds:
- it **has methods** (its method set is meaningful — the `FirstClassFunctions`/`hashFunc` wrap case
  below still applies);
- it is **generic** (it is referenced as `Seq<V>`, and the type parameter must stay in scope — see
  the generic-`Seq` range-over-func case);
- its **signature references another named func type, including itself**. A *self-referential* func
  type — `type stateFn func(*machine) stateFn` (a Go state machine, `NamedFuncTypeStateMachine`) —
  has no finite base-delegate form (`Func<M, Func<M, …>>` is infinite); and a reference to another
  named func type (`strategy func(score) action`) would leave that name undefined after collapse.
  Only the *leaves* of the func-type reference graph collapse; a referencing type stays named and
  renders the collapsed leaf inside its own signature.

Because the collapse applies at both the declaration and every reference, and to foreign types too
(context's `CancelFunc` collapses in context's own conversion, so database/sql sees `Action`),
consistency holds across packages. One position needed a companion fix: a variadic `...Option`
element is package-class-qualified (`main_package.Option`) for a package-local named type, which
would mangle a collapsed delegate to `main_package.Action` (CS0426) — `variadicElementType` now
skips the qualifier when the element collapsed. Cleared 13 of database/sql's 17 errors (the whole
named-func family + the CS1929 it masked). Guarded by `MethodlessFuncType` (a function returning
the named type, one taking the anonymous underlying, a struct field, and a tuple-deconstruction
seam across the two); regression-checked against the self-referential (`NamedFuncTypeStateMachine`,
unchanged), nested-reference (`FirstClassFunctions`), and variadic-param (`PublicizedFuncTypeParam`)
cases.

**A collapsed methodless func type must NOT export a `[GoTypeAlias]`.** When such a type is *also*
collision-renamed — `type Filter func(...)` alongside a method `Filter` (go/ast's `Filter` vs
`(CommentMap).Filter`; the `ReservedTypeMethodCollision` shape) — the rename records an exported
`[assembly: GoTypeAlias("Filter", "ΔFilter")]` so consumers can name the renamed type. But because
the type collapses to its base delegate, **no `<pkg>_package.ΔFilter` type is ever emitted** — so a
consumer that loads the alias generates `global using astꓸFilter = go.go.ast_package.ΔFilter;`
naming a nonexistent type (go/doc referencing `ast.Filter`, CS0426). `visitFuncType` now records
each collapsed methodless func type's name in `packageInlineFuncTypeNames`, and the exported-type-
alias emission skips any alias whose key *or* value matches (the collision path stores the alias
under the renamed value `ΔFilter`, the plain path under the raw name) — so the alias is never
exported and the consumer renders `ast.Filter` inline as `Func<nint, bool>` through the normal
collapse. (Guarded by the `CrossPkgUser` extension — a cross-package `CrossPkgLib.Sift` methodless
func type colliding with a `Sift` method, named as a var type and rendered inline, output vs Go; and
by `ReservedTypeMethodCollision` whose `[GoTypeAlias]` is now correctly absent.)

When such a collapsed delegate's signature carries a parameter whose type lives in a **sub-package**
(an import path with a slash), the `Func<…>`/`Action<…>` rendering must qualify that type as the
package **class**, not the namespace. The collapsed signature is produced from the Go signature's
`t.String()`, which keeps the canonical import PATH inline — `func(*sync/atomic.Int32) int32`,
`func(string, io/fs.DirEntry, error) error` (path/filepath's `WalkDirFunc`) — losing the file's import
alias. `convertToCSFullTypeName` converted the whole slash-bearing string as one import path, dotting
the type straight into the namespace: `sync.atomic.Int32` / `io.fs.DirEntry` — CS0234, since `atomic`
is not a namespace of `go.sync` (the type lives in class `atomic_package`). It now splits the trailing
`.TypeName` off at the first `.` after the last path `/`, converts the package path with the class
suffix, and re-appends: `sync.atomic_package.Int32`, `io.fs_package.DirEntry`. The suffix is only added
when the path segment does not already carry it — some callers (a recorded `[GoType]` underlying,
`sync/atomic_package.Uint32`) hand a pre-suffixed path, which would otherwise double to
`atomic_package_package` (a `DefinedTypeOverPkgType` regression, caught and gated). The behavioral
corpus is byte-identical except the intended change, and an A/B reconvert of net+go/types (same package
set) is byte-identical — only the func-type-subpackage-param shape moves. (Guarded by the
`SubpackageFuncTypeParam` behavioral test — a methodless `applyFunc func(*atomic.Int32) int32` whose
collapsed delegate carries the `sync/atomic` sub-package parameter, output-compared vs Go; the same
shape drives path/filepath's `WalkDir`/`Walk` referencing `io/fs.DirEntry`/`FileInfo`.)

A collapsed func type's **parameter list must not be double-converted**. `convertToCSFullTypeName`'s
`func(` handler split the parameter string with `extractTypes`, then re-ran `convertToCSTypeName` over each
result — but `extractTypes` already renders a NAMED parameter in C# form (it strips the Go name and converts
the type). Re-feeding an already-C# `map<@string, ж<Object>>` through the `map<` arm's `splitMapKeyValue`
mis-parsed it into `map<@string, ж<Object>, >` — a spurious trailing empty type arg (CS1031 "Type expected",
go/ast's `NewPackage` taking `type Importer func(imports map[string]*Object, path string) (…)`). The fix makes
`extractTypes` **always** return C#-form (the bare-type/unnamed branch now converts in place too, matching the
named branch), and the caller trusts that output directly instead of a second pass. This is byte-identical
everywhere except named-parameter func types — bare-type func types (`func(int, string)`) were already
converted once and stay so, just at the `extractTypes` site rather than the caller. (Guarded by the
`NamedFuncTypeMapParam` behavioral test — `type Importer func(imports map[string]*Node, path string) (pkg
*Node, err error)` used as a function parameter, output-compared vs Go; CNR byte-identical across the corpus,
and an A/B reconvert of go/ast shows only that one collapsed-delegate parameter shape moving.)

**A collapsed methodless named func type's DELEGATE TYPE renders through `iifeDelegateType`, not the string
path.** The double-conversion fix above kept the collapsed delegate on `convertToCSFullTypeName`'s `func(`
string handler — but that string domain naively slash→dots a cross-package element's import PATH. go/doc
passes `simpleImporter` to `ast.NewPackage` (whose `importer` is `ast.Importer`, a methodless func type), so
the converter wraps the method group in the collapsed base delegate
`new Func<map<@string, ж<go.ast.Object>>, @string, (ж<go.ast.Object> pkg, error err)>(simpleImporter)` —
`go/ast.Object` mangled to `go.ast.Object` (no `_package` class, no file alias), so `ast` is not a namespace
of `go` (CS0234 ×2), and the resulting error-typed delegate then fails the method-group→delegate conversion
(CS0123). `getCSharpTypeName` now routes a methodless named func type through the SAME structural
`iifeDelegateType` path an ANONYMOUS signature already takes (that path exists precisely because the string
path mangles slash-bearing package paths), naming each element via `aliasedElementTypeName` — so the
cross-package `ast.Object` keeps its `ast` alias (and a Δ-renamed foreign element its recorded `ꓸ`-alias):
`new Func<map<@string, ж<ast.Object>>, @string, (ж<ast.Object>, error)>(simpleImporter)`. The only visible
change for a SAME-package/single-segment element is that a multi-result signature's delegate type drops its
Go result-tuple element NAMES (`(ж<Node> pkg, error err)` → `(ж<Node>, error)`), matching how anonymous
signatures already render — cosmetic, both compile. An A/B full-stdlib reconvert moves **11 files, all
equal-or-better**: the go/doc mangle fixed, plus `go/parser`/`go/scanner` (`go.token_package.ΔPosition` →
`tokenꓸPosition`), `go/internal/gccgoimporter` (a malformed `(io.ReadCloser>, error)` → valid),
`internal/trace/traceviewer` (`net.http_package.Request` → `http.Request`), and `path/filepath`
(`io.fs_package.DirEntry` → `fs.DirEntry`) all cleaned up, with `bufio`/`go/ast`/`nettest` only dropping
cosmetic tuple names; CNR touches only three existing goldens (`NamedFuncTypeMapParam`,
`SubpackageFuncTypeParam`, `FirstClassFunctions`), all the same pattern. (Guarded by the `CrossPkgUser`
extension — a package-level `simpleResolve` passed as a METHOD GROUP to `CrossPkgLib.Resolve`, whose
`Resolver` is a methodless func type naming the cross-package `*CrossPkgLib.Node`, so the wrapped delegate
renders `ж<CrossPkgLib.Node>` via the alias, output-compared vs Go. The single-segment producer compiles
either way, so the byte-golden — unnamed vs Go-named result tuple — is what guards the routing; the exact
slash-bearing CS0234/CS0123 needs a multi-segment producer like go/ast, verified by the go/doc source A/B.
go/doc's own remaining block is the SHARED generated-adapter forwarding of go/ast's unexported interface
marker methods — a separate root.)

A companion root cleared path/filepath fully: a **cross-package type ALIAS whose target lives in yet
another package** — `os.FileInfo = fs.FileInfo` (os/types.go, target in `io/fs`) — is emitted as an
assembly-scoped `global using FileInfo = go.io.fs_package.FileInfo;` in **os's own** conversion, never as
a member of the os package's C# class, so a cross-package reference `os_package.FileInfo` does not resolve
(CS0426, path/filepath's `lstat = os.Lstat` func value). `getAliasQualifiedTypeName` now renders such an alias as its
**target** — `os.FileInfo` → `fs.FileInfo` (→ `io.fs_package.FileInfo` via the file's `fs` using). Gated
to a **different-package target**: an alias to a SAME-package type (`CrossPkgLib.Temperature = Celsius`)
already resolves through the existing `ꓸ` global-using alias (`CrossPkgLibꓸTemperature`) and is left
untouched — narrowing here reverted a `CrossPkgUser` churn the blanket form caused. CNR byte-identical;
an A/B of os+io/ioutil (same package set) shows only that one intended resolution (io/ioutil's `ReadDir`
sort lambda moved `osꓸFileInfo` → `fs.FileInfo`, matching the file's other `fs.FileInfo` refs — still
compiles). **GUARD OWED** — the shape needs three packages (B declares `Y`, A aliases `type X = B.Y`, C
references `A.X`), which neither the single-package baseline nor the 2-package `CrossPkg` harness
expresses; validated by the `core/path/filepath` build (1→0) + io/ioutil build.)

**A func type renders structurally in EVERY type-name path — the signature never stringifies.**
`getAliasQualifiedTypeName` now carries a `*types.Signature` arm (`signatureTypeName`, beside `iifeDelegateType`)
mirroring the slice/map/chan composite arms: Go syntax — `func(name type, …) results` — with every
parameter/result type resolved **recursively**, so a cross-package element keeps the file's short
import alias exactly like the neighboring map/slice fields. Previously only *some* positions routed
through the structural `iifeDelegateType` (var declarations; variadic or slash-bearing struct
fields); every other position — a struct field of a **named** methodless func type (go/importer's
`importer gccgoimporter.Importer`), a MAP field's func **value** type (net/http's `TLSNextProto`
maps), a same-package named func field (traceviewer's `f MutatorUtilFunc`) — reached
`convertToCSFullTypeName` as `t.String()` text with import PATHS inline, and the slash heuristics
mangled those one of **three ways** depending on the string's shape: the whole-string
path-conversion arm fires when no dot-after-slash precedes the first `[` (a leading `map[` bracket),
naively dotting every path — `ж<go.types.Package>`, `ж<crypto.tls.Conn>` (no `_package` class, and
under a `go.go`-nested namespace the leading segment binds the child namespace — CS0234) — while the
split-at-dot arm mangles a mid-signature path to a classed-but-unrooted form
(`@internal.trace_package.UtilFlags`, traceviewer mmu.cs). With the structural arm the string
reaching the parser is slash-free (`func(*Server, *tls.Conn, Handler)`) and each element converts
through the normal alias route. Result NAMES are preserved, so a named multi-result field keeps its
named C# tuple (the display-path advantage the old struct-field routing existed to protect); a
same-package/builtin signature renders byte-identically to the old `t.String()` path (zero churn —
CNR confirmed across all 331 behavioral projects). A variadic tail renders `...elem` (which the old
path's `..`-strip reduced to the unparseable `.elem`) and lowers through the parser to the golib
`ꓸꓸꓸ` delegate family (next paragraph). One side effect: element recursion passes through
`getAliasQualifiedTypeName`'s foreign-ALIAS arm, so a signature naming a cross-package alias (`os.FileInfo` →
`io/fs.FileInfo`) now registers the **target's** package for a file-local using — a few stdlib files
gain a benign `using fs = …;` alias line (`collectTypePackages`' Named case does not match a
`*types.Alias`, so the old path never registered it). Whole-stdlib A/B footprint: 20 files — the
go/importer field fixed, `ж<tls.Conn>` in net/http server/transport/h2_bundle (field + composite
literals), traceviewer's `Func<trace.UtilFlags, (slice<slice<trace.MutatorUtil>>, error)>`,
go/scanner's `err` field moving to the canonical `tokenꓸPosition` alias (the old
`go.token_package.ΔPosition` resolved only by go.go-namespace luck), the variadic type-assert
target below, one comment-alignment shift, and the benign using-line additions. Cleared the
IMP-2/HTTP-3 CS0234 cluster (net/http ×8 + go/importer ×6 + traceviewer). (Guarded by the
`SynthesizedDelegateChildPkg` behavioral test — a nested CHILD subpackage (slash-bearing import
path) whose `*inner.Record` rides a named methodless func-type field with a nested-tuple lookup
param AND a `map[string]func(*inner.Record, string)` field, both invoked at runtime vs Go.)

**The func-type string parser splits parameters at TOP-LEVEL commas only — and a variadic tail
lowers to the `ꓸꓸꓸ` delegate family.** `extractTypes` split the parameter list with a naive
`strings.Split(signature, ",")`, so a nested func param returning a TUPLE — `lookup func(string)
(io.ReadCloser, error)` (go/internal/gccgoimporter's `Importer`, surfacing as go/importer's
`gccgoimports.importer` field) — shredded at the tuple's interior comma, unbalancing the assembled
delegate: `Func<@string, (io.ReadCloser>, error)` (the inner `>` closes before the tuple's second
element — a 6-error syntax cascade, IMP-1). `splitTopLevelParams` tracks `<>`/`()`/`[]`/`{}` depth
(with the channel-arrow `<-` guard `splitMapKeyValue` already carries) and splits only at depth 0.
On top of that, a variadic tail (`...elem`, from the structural render above) converts its ELEMENT
type in `extractTypes` and carries an ellipsis-family marker that the `func(` assembler hoists into
the delegate FAMILY name — `Actionꓸꓸꓸ<@string, any>` — mirroring `iifeDelegateType`'s lowering
exactly. That fixed the variadic func type as a type-ASSERTION target as a rider:
`.(func(string, ...any))` (net/http transport.go's `tLogKey` logger) previously emitted the
unparseable `._<Action<@string, .any>>(ᐧ)` and now renders `._<Actionꓸꓸꓸ<@string, any>>(ᐧ)`.
(Guarded by the `FuncFieldNestedTupleParam` behavioral test — builtin-typed struct fields with
nested-func-returning-tuple params in both the anonymous and named-collapse forms plus a
named-tuple-result sibling, all invoked at runtime vs Go.)

A **type ASSERTION** whose target is a methodless func type must assert against the **collapsed
delegate**, not the (never-emitted) name. `ci.(Compressor)` where
`type Compressor func(io.Writer) (io.WriteCloser, error)` (archive/zip's compressor/decompressor
registries) rendered `ci._<Compressor>()` — `convTypeAssertExpr` converts the target via `convExpr`,
which emits the bare ident, and after collapse `Compressor` is undefined (CS0246). When the asserted
target is a methodless named func type, the assertion now renders its `getCSharpTypeName` (the collapsed
`Func<…>`): `ci._<Func<io.Writer, (io.WriteCloser, error)>>()` — matching how the stored value was
emitted (a collapsed delegate). Other assertion targets are unchanged. (Guarded by the
`MethodlessFuncTypeAssert` behavioral test — `i.(Compressor)` on a matching and a non-matching dynamic
type, output-compared vs Go; CNR byte-identical and an A/B of archive/zip shows only the two intended
`_<Compressor>`/`_<Decompressor>` → `_<Func<…>>` lines.)

An **UNINITIALIZED local `var` of a methodless named func type** renders its declared type through the
same structural path. `visitValueSpec`'s no-initializer branch computed the type from
`convertToCSTypeName(getAliasQualifiedTypeName(...))` (the string path) and only re-routed a bare *anonymous*
`*types.Signature` through `getCSharpTypeName`; a methodless NAMED func type is a `*types.Named`, so it kept
the string render — and that render mangles a slash-bearing cross-package element. go/parser's `parseDecl`
declares `var f parseSpecFunction`
(`type parseSpecFunction func(doc *ast.CommentGroup, keyword token.Token, iota int) ast.Spec`), which
emitted `Func<ж<go.ast.CommentGroup>, go.token.Token, nint, go.ast_package.Spec> f = default!;` — the
`go.ast`/`go.token` elements re-root to the nonexistent `go.go.ast`/`go.go.token` (CS0234), and the
declared delegate then mismatched the lambdas assigned to `f` and the `parseGenDecl(keyword, f)` parameter,
which render the SAME Go types structurally as `ast.CommentGroup`/`token.Token` (CS1661/CS1678/CS1503 — 12
errors, all this one declaration). The no-initializer branch now routes a func-typed var (anonymous
signature OR methodless named func, via `methodlessNamedFuncSignature`) through `getCSharpTypeName` →
`iifeDelegateType`, whose `aliasedElementTypeName` keeps each element's `pkg.Type` alias:
`Func<ж<ast.CommentGroup>, token.Token, nint, ast.Spec> f = default!;`. This precedence matches
`getCSharpTypeName`'s own — the func render wins over the foreign-alias route (which for a methodless named
func would point at the SKIPPED delegate declaration); a non-func foreign-renamed local keeps its alias
unchanged. An A/B full-stdlib reconvert moves exactly one file (go/parser/parser.cs), greening go.parser
outright. (Guarded by the `MethodlessFuncType` extension — an uninitialized `var find lookup` where
`type lookup func(string) (path string, ok bool)`; the byte-golden captures the structural render
`Func<@string, (@string, bool)>` — dropping the Go result NAMES the string path keeps — output-compared vs
Go. As with the delegate-routing sibling above, a single-segment/same-package producer compiles either way,
so the unnamed-vs-Go-named result tuple is what guards the routing; the exact slash-bearing CS0234 needs a
multi-segment producer like go/ast, verified by the go/parser source A/B.)

### Named delegate types wrap mismatched initializers
A NAMED func-type field initialized with a value of a DIFFERENT delegate type has no implicit
C# conversion: internal/concurrent's `keyHash: mapType.Hasher` feeds a `hashFunc` field from a
`Func<…>` field. The composite-literal walk resolves each element's field BY NAME (keyed-aware)
and wraps mismatched delegate values in the target delegate's constructor —
`keyHash: new hashFunc((~mapType).Hasher)` (the wrap splits a C# named-argument label first).
FuncLit and nil initializers stay bare. Guarded by `FirstClassFunctions`
(`handler`/`provider`/`registry`).

### A named delegate value passed to a structural func parameter re-wraps
The MIRROR of the argument-position named-delegate wrap: a **structural** (written-anonymous) func
parameter receiving a value of a **named** delegate type — net/http h2_bundle's
`sc.scheduleHandler(…, handler)`, where `handler` is `HandlerFunc` and the parameter is
`func(ResponseWriter, *Request)` (CS1503). Go converts named→structural implicitly; C# needs the
same delegate re-wrap, targeting the synthesized structural delegate:

```go
type Handler func(int, string) string   // has a method → distinct C# delegate
func invoke(f func(int, string) string, n int, s string) string { return f(n, s) }
var h Handler = describe
invoke(h, 1, "a")
```
```csharp
invoke(new Func<nint, @string, @string>(h), 1, "a"u8);
```

Two argument shapes render named and take the wrap: a value whose **Go type** is a named func type
(with methods), and a `:=` local **declared from a method group**, which the declaration emission
types with the matching package named delegate (`HandlerFunc handler = Ꮡsc.Value.handler.ServeHTTP;`
— the bare-function-value `:=` rule above) even though go/types keeps it structural — the exact
h2_bundle shape. A **methodless** named func type already *renders* as the structural delegate
(`methodlessNamedFuncSignature` collapses it — same C# type), so it stays bare; method groups and
func literals themselves convert natively. A generic structural parameter (unsubstituted type
params) also stays native. (Guarded by the `NamedDelegateStructuralParam` behavioral test —
named-with-method and method-group-declared locals wrapped, methodless/method-group/func-literal
controls bare, values vs Go.)

The same mirror applies to a **composite-literal FIELD** (2026-07-17; sort's test-suite
conversion): the composite walk previously wrapped only the named-field ← different-delegate
direction, so GOROOT sort example_keys_test's `planetSorter{planets: planets, by: by}` — a `By`
value (named, with a `Sort` method) initializing the written structural field `by func(p1, p2
*Planet) bool` — emitted the bare `by: by` against the `Func<ж<Planet>, ж<Planet>, bool>`
constructor parameter (CS1503; the Phase-4 blocker-map row B10b). The structural-field arm now
applies the identical named-rendering test and wrap: `by: new Func<ж<Planet>, ж<Planet>,
bool>(by)`. Method groups, func literals, and nil stay bare, and generic fields stay native, as
at call sites. (Guarded by the `NamedFuncTypeStructuralField` behavioral test — the By-with-method
sorter pattern wrapped, a method-group field initializer control bare, values vs Go.)

### Func-typed fields with a cross-package (slash-path) type render structurally
A func-typed struct field whose signature names a type from a **multi-segment** import path —
testing/quick's `Config.Values func([]reflect.Value, *rand.Rand)`, where `rand` is `math/rand` —
must render as a structural `Action`/`Func<…>` delegate via `getCSharpTypeName`, not through the
string display path. The display path stringifies the signature as `func([]reflect.Value,
*math/rand.Rand)` and splits the slash-bearing import path on `/`, emitting the dotted
`math.rand.Rand`; but `math` aliases to `math_package`, so `math.rand` resolves to the nonexistent
`math_package.rand` (CS0426). The structural renderer recurses per signature element and qualifies
each named type by its package **name**:

```go
type Config struct {
    Values func([]reflect.Value, *rand.Rand)   // rand is math/rand
}
```
```csharp
public Action<slice<reflectꓸValue>, ж<rand.Rand>> Values;
```

The re-routing is gated on the signature string containing `/` **or the signature being variadic**:
the string path cannot render a variadic signature at all — `getAliasQualifiedTypeName`'s `..` strip reduces the
ellipsis of go/build's `JoinPath func(elem ...string) string` (Context, build.go:84) to `.string`,
emitting the unparseable `Func<.@string, @string>` (CS1031 + CS1003 ×2, all three go.build errors),
and even unstripped it has no variadic lowering. Structurally such a field renders the golib
variadic delegate family (`public Funcꓸꓸꓸ<@string, @string> JoinPath;` — see the variadic-lowering
section below), which loose-arg, empty and spread calls through the field all bind. Every other
func field keeps the display path: `func(string) (importPath string, ok bool)` preserves its named
tuple elements that the structural renderer drops. (Guarded by the `FuncTypeParam` behavioral
test's `runner.gen` field, and by `VariadicFuncFields` — a struct with variadic func-typed fields
assigned from a named func and func literals, called loose/empty/spread — for the variadic arm.)

### A variadic func type lowers to the golib `Actionꓸꓸꓸ`/`Funcꓸꓸꓸ` delegates

A **variadic function TYPE used as a value** — a parameter, variable, struct field, or collapsed
methodless named type such as go/types' `reportf func(format string, args ...interface{})` — used
to have three mutually incompatible lowerings: the delegate type rendered `Action<@string,
slice<any>>` (no `params` — the BCL `Action` cannot express one), a variadic func LITERAL emitted
the named-function convention `(@string format, params ꓸꓸꓸany argsʗp) => …` (CS1661/CS1678
against that `Action`), and calls through the value passed loose Go-style args as if `params`
existed (`reportf("…"u8, (~f).typ)` — CS1503; `reportf("empty type set"u8)` — CS7036).

The lowering now targets a golib delegate family carrying a real C# 13 `params Span<T>` tail
(`src/core/golib/variadic.cs`; fixed-arity prefixes up to eight mirror the BCL Action/Func family,
and the `ꓸꓸꓸ` suffix reads as Go's `...`):

```csharp
public delegate void Actionꓸꓸꓸ<T1, TArg>(T1 arg1, params Span<TArg> args);
public delegate TResult Funcꓸꓸꓸ<T1, TArg, out TResult>(T1 arg1, params Span<TArg> args);
```

`iifeDelegateType` — the single structural lowering every `getCSharpTypeName(*types.Signature)` and
collapsed methodless named func type routes through — names the family when `sig.Variadic()` and
passes the variadic **element** type as the last type argument. Everything else then agrees with
**zero changes** to the other emissions, because the parameter types match by identity
(`ꓸꓸꓸT` *is* `Span<T>`):

- the named-function convention (`internal static @string gather(@string prefix, params ꓸꓸꓸnint
  valsʗp)`) converts as a method group — `apply(gather)` stays bare;
- a variadic func literal (`(@string prefix, params ꓸꓸꓸnint valsʗp) => …`, C# 13 params lambda)
  converts natively — go/types' `comparable(typ, true, default!, (@string format, params ꓸꓸꓸany
  argsʗp) => {…})` now binds its `Actionꓸꓸꓸ<@string, any>` parameter;
- calls through the value pass loose args or an empty tail via C# `params` expansion, and a Go
  spread (`f(nums...)`) binds the slice's `.ꓸꓸꓸ` Span in normal form;
- a C# consumer calls a transpiled printf-style callback naturally (`ctx.Logf("…", a, b)`) — the
  library use case that ruled out the pack-into-a-`slice<T>` alternative.

A `:=`-declared variadic func literal is untouched: it keeps C#'s natural (params-capable) lambda
type under `var` (the `VariadicClosureSpread` shape). One deliberate residue: `defer`/`goǃ` of a
call **through a variadic func value** would need to capture the `Span` tail, which a ref struct
cannot be — no stdlib occurrence; pack into a slice at such a site if one ever appears. Full-stdlib
A/B footprint: go/types predicates.cs/expr.cs plus every file that renders a variadic func type
structurally (inspected file-by-file at introduction). (Guarded by `VariadicFuncValues` — a named
func AND a func literal satisfying a variadic func-typed param, loose/empty/spread calls through
it, and a nil-compared variadic func-typed var — output-compared vs Go.)

**A type-ASSERTION target routes through the same structural lowering.** `convTypeAssertExpr` rendered
the asserted type by converting the TYPE EXPRESSION through the string-based type-name path, which
skips the variadic lowering above — net/http transport.go's
`cw.(func(string, ...any))` emitted `._<Action<@string, .any>>(ᐧ)` with a literal `.any` (CS1001, the
`...` mangled instead of lowered). An anonymous-signature assert target now renders through
`getCSharpTypeName` → `iifeDelegateType`, exactly like the collapsed methodless NAMED func target already
did: `._<Actionꓸꓸꓸ<@string, any>>(ᐧ)`. Non-variadic signatures render identically on both paths, so the
only full-stdlib delta is the transport.cs site. (Guarded by `VariadicFuncTypeAssert` — a positive
variadic assert invoked through the asserted value, a negative assert on a non-func value, and a
non-variadic anonymous func assert, output-compared vs Go.)

### Major-version import directories
A `/vN` import path segment (math/rand/v2) hosts a package named for the PARENT segment, so the
emitted class follows the package NAME: consumers reference `go.math.rand.rand_package`, and the
namespace is `go.math.rand` — never the path-derived `v2_package` / `go.math.rand.v2`. Go's own
convention (the directory is a version marker, not the package identifier) means the package name
equals the second-to-last path segment, and every place the converter derives a class/namespace/
alias from a `/vN` import path must honor it. There are **four** such derivations, reached by
different renderers, and each needed the convention applied at its own site:

1. **`using`-alias + namespace emission** — `convertImportPathToNamespace` (`visitImportSpec.go`)
   rewrites the last path part to the parent segment via `majorVersionSegmentRegex`, so the file's
   `using rand = go.math.rand.rand_package;` and the package's own `namespace go.math.rand` agree.
2. **`t.String()`-based FQ type rendering** — `getAliasQualifiedTypeName` / `getFullyQualifiedTypeName` (`main.go`) build a
   foreign type's name from the type graph's path-qualified string, whose last segment slash-strip
   assumes the path tail IS the package qualifier. For a `/vN` tail it left the version behind
   (`math/rand/v2.Rand` → `v2.Rand`), which the alias-prepend then doubled into `rand.v2.Rand`
   (`v2` read as a member of class `rand_package` — CS0426). Both renderers now reduce the foreign
   import-PATH qualifier to the package NAME before the slash-strip. `getFullyQualifiedTypeName` also composes
   `pkg.Path()+"_package"` directly for the qualified base name — routed through `packageClassPath`,
   which swaps a `/vN` tail for the Go package name.
3. **Cross-package reference metadata** — `PackageInfo.RootPackageName` (`importOperations.go`) is the
   code-facing qualifier that keys imported-alias loading and the foreign-implement records that cast
   sites reference (`GoImplement<…rand_package.PCG, …rand_package.Source>(Pointer = true)`). It was the
   path's last segment (`v2`); `rootPackageNameFromPathParts` now returns the parent segment for a
   `/vN` tail. `PackageName` stays path-formed — it also names the referenced `.csproj`, which IS
   `math.rand.v2.csproj`.
4. **Imported type-alias TARGET class** — `loadImportedTypeAliases` (`importOperations.go`) qualifies
   an imported alias's target as `go.<PackageName>_package.<Type>`; the class path is `PackageName`
   with its final segment replaced by `RootPackageName`, so a `/vN` producer's exported aliases
   resolve to `rand_package`, not `v2_package`.

The convention is that a package literally named `vN` would instead need the type-graph name; the
stdlib has none, so the regex/parent-segment rule holds corpus-wide. Guarded by the `VersionedImport`
behavioral test — a `main` importing a sibling `vlib/v2` module (`package vlib`) that mirrors
math/rand/v2's shape: a struct field `ж<vlib.Rand>` (renderer #2), a `*PCG → Source` pointer cast
recorded as `go.vlib.vlib_package.PCG` (#3/#4), output-compared vs `go run` across all four phases.
This is what unblocks sort as Phase 4's second validated package (its test suite imports math/rand/v2).

### A C# keyword inside a dotted import-path element
A Go import-path element may itself contain **dots** — a module host (`gopkg.in`, `example.com`,
`golang.org`) or a versioned tail (`yaml.v3`) — and every one of those dots is a **namespace
separator** in the emitted C#. So `gopkg.in/yaml.v3` does not render two namespace levels, it renders
four, and each is a separate C# identifier that has to be keyword-escaped on its own.

Two sanitizers render namespace text, and until 2026-08-07 only one of them knew that. The
**declaration** side (`getProjectName` → `getCoreSanitizedIdentifier`) has always split an element on
its dots, so the dependency's own file correctly opens `namespace go.gopkg.@in;`. Every **consumer**
emission — the import's `using yaml = …;` alias, the enclosing-namespace `using gopkg.…;` an unaliased
import adds, the child-namespace map that decides root qualification, and the string-path type
renderer — goes through `convertImportPathToNamespace`, which sanitized each `/`-split element with
`getSanitizedImport`, measuring it **whole**. Whole, `gopkg.in` is not a C# keyword, so it passed
through bare and the importer emitted

```csharp
using yaml = gopkg.in.yaml_package;   // CS1001/CS1002/CS1022 — `in` is a keyword
using gopkg.in;
```

against a producer that had named itself `go.gopkg.@in`. The dependency compiled; nothing that
imported it could. (Issue #33: `gopkg.in/yaml.v3` converts, then does not build.)

The fix is one function, both sides: **`getSanitizedImport` splits on dots too**, escaping each level
independently — exactly what the declaration side does. The recursion stays inside `getSanitizedImport`
rather than deferring to `getCoreSanitizedIdentifier`, because callers append the `_package` class
suffix to the final element before calling and the core sanitizer `Δ`-prefixes anything ending in
`_package`; that swap would emit `Δyaml_package`, a class no producer declares. Escaping is idempotent
(an already-`@`-marked part returns unchanged), so re-sanitizing a rendered namespace is stable.

The behavior change is exactly "a dotted input with a keyword sub-token is now escaped": a string
containing a dot could never equal a keyword, so the old whole-string test never fired for one, and
hyphen/tilde replacement is per-part identical either way. Emission-neutral for both corpora, and
measured so — the behavioral corpus has no dotted module path at all, and the standard library's only
dotted element is `golang.org` (the GOROOT-vendored tree), whose `golang`/`org` are not keywords:
[CNR](Glossary.md#cnr) byte-identical across 572 packages, and a seeded full reconvert byte-identical
across 5,179 `.cs`/`.csproj`/`README.md` plus the generated `go2cs-stdlib.slnx`.

Guarded at both altitudes: `TestGetSanitizedImportKeywordSegments` (`sanitization_test.go` — several
keywords in several positions, the two sanitizers asserted to agree on a segment, the `_package`
suffix asserted NOT to be `Δ`-prefixed, and idempotence) and `TestRecurseKeywordNamespaceSegment`
(`moduleConverter_integration_test.go` — network-free, an unaliased import of a `gopkg.in`-shaped
dependency, asserting the producer's declaration and both consumer emissions name the same namespace,
then sweeping every `using` in the file against the converter's own `keywords` set so a keyword the
fixture never exercises is covered by the same assertion). Both neuter-proven: restoring the
whole-string measurement reproduces the reporter's emitted text verbatim.

### The import-path rewrite rewrites only the PATH, not the constructor in front of it
The string-path type renderer (`convertToCSFullTypeName`) peels a Go type expression one constructor
at a time — `<-chan `, `chan `, `chan<- `, `*`, `[]`, `[N]`, `map[K]`, `func(…)` — recursing on what
is left. Its **import-path rewrite runs first**, before any of those branches, because a
package-qualified element has to become a C# namespace before the name means anything. Until
2026-08-08 that rewrite measured the path from index 0 of the whole string, constructor included.

`convertImportPathToNamespace` maps a **hyphen to an underscore**, because a Go path element may
legally contain one (`mongo-driver`, `go-isatty`). Handed the constructor as well, it rewrote the
`-` of `<-chan` too. The declaration in `go.mongodb.org/mongo-driver/x/mongo/driver/session`

```go
type Pool struct { descChan <-chan description.Topology }
```

renders fully-qualified as `<-chan go.mongodb.org/mongo_driver/mongo/description_package.Topology`,
and came back as `<_chan go.mongodb.org.mongo_driver.…`. No channel branch recognizes `<_chan`, so it
fell through to the **array** branch, which slices past the `>` that a `[N]` length closes — and with
no `>` in the string at all, `strings.Index` returned -1 and the slice was `typeName[0:]`. The
renderer re-entered on the IDENTICAL string, without bound: `fatal error: stack overflow`, taking a
1,726-package `-recurse` run down at package 1456 (issue #33's third report).

A **single-segment** path never had a slash to enter the rewrite, so `<-chan time_package.Time` was
always correct. That is the whole reason the standard library — which is nothing but single- and
multi-segment *stdlib* paths, none of them hyphenated behind a `<-chan` — never saw this, and only a
module dependency could.

The fix is `importPathStart`: find where the path begins by scanning **backward** from the candidate
region to the first byte no import path may contain, and rewrite only from there. `-` cannot be that
delimiter (it would split `mongo-driver` mid-path), but every constructor the renderer emits ends in
one that can — a space (`<-chan `, `chan `, `chan<- `), `*`, `]` (`[]`, `[2]`, `map[K]`) or `(`
(`func(`). `<-chan ` then survives for its own branch, which recurses on the bare qualified element
exactly as it always has, and `descChan` emits
`/*<-*/channel<go.mongodb.org.mongo_driver.mongo.description_package.Topology>` against the
`using description = global::go.go.mongodb.org.….description_package;` the same file writes.

Two subtleties the first cut got wrong, both caught by [CNR](Glossary.md#cnr):

- **A non-ASCII byte is a path byte.** Every delimiter this scan looks for is a constructor
  character and all of them are ASCII, but the converter's own synthetic markers are not (`ᴛ`, `ж`,
  `Ꮡ`, `ꓸ`, `Δ`). Treating a multi-byte rune as a delimiter stranded the scan *inside* the type
  name, freezing the path in front of it: `go.main_package/entryᴛ1` for
  `go.main_package.entryᴛ1`. The `PublicizedInterfaceAnonAlias` and `NestedAliasUser` lifted-alias
  goldens are what surfaced it.
- **The scan stops at the generic-argument bracket.** Past it lie type ARGUMENTS whose `,`, space
  and `]` are not constructor text and would strand the scan at the tail of the string.

**Known residual.** When the OUTERMOST constructor is `[]`, its leading `[` is read as the start of a
generic argument list and truncates the path scan to nothing, so `[]<-chan <module path>.T` is still
mangled. Fixing it means no longer treating a *leading* `[` as a generic bracket, which re-routes
every `[]<pkg>/<sub>.T` in the corpus through the other branch (a `_package`-suffix change) — a
corpus-wide emission change that does not belong in a crash fix. It no longer crashes, which is the
part that mattered: see below.

**The crash-proofing is separate from the rendering fix, and is the part that generalizes.** The
array branch now requires the `>` it slices past. A Go stack overflow is a **fatal** runtime error,
not a panic, so the conversion driver's per-file `recover` could not contain it and one unrenderable
type killed the whole run instead of its own package. Bounded, an unrecognized shape is reported by
name on stderr (`Cannot render a C# type name for the unrecognized type expression "…"`) and the
package still converts. Every other branch consumes at least one byte before recursing, so bounding
this one bounds the renderer.

Guarded at both altitudes. `typeNameResolution_test.go` pins the renderer: `TestImportPathStart`
(each constructor, the marker runes, and the paths that must NOT move),
`TestConvertToCSFullTypeNameConstructedModulePaths` (the reported field in all three channel
directions, `*`/`[]`/`[N]`/`map[K]`/nested, plus the single-segment and bare-path cases that must stay
byte-identical, plus the residual above pinned as a decision), and `TestUnclosedBracketTerminates`,
which runs in a **child process** with a 4 MB stack because the condition it guards is unrecoverable
in-process. `TestRecurseChannelOfHyphenatedModulePath`
(`moduleConverter_integration_test.go`) pins that a real declaration of the reported shape reaches
that renderer through an actual `-recurse`, over a network-free fixture whose module path mirrors the
report's — hyphenated first segment, multi-segment tail. Its fixture carries a type **alias** to the
channel alongside the struct field, deliberately: a field DECLARATION emits the readable file-local
alias (`description.Topology`), so the fully-qualified render is computed but never written and a
test reading only the field cannot tell a correct render from a mangled one — while an exported alias
writes the fully-qualified string verbatim into both `main.cs` and the `[GoTypeAlias]` record.

Neuter-proven three ways: both reverted reproduces the reported `fatal error: stack overflow`; the
bound alone reverted fails the child test in 0.02 s; the rewrite alone reverted (bound in place)
makes the converter print the warning, exit 0, and still write every `.cs` — the crash-proofing
demonstrated independently of the rendering — while the integration test fails on the emitted
`global using TopoChan = go.<_chan example.com.mongo_driver.…;`.

### A non-canonically-aliased import renders foreign types via the file's alias
A file that imports a package under an **explicit alias that differs from the canonical package
name** must render that package's types through the alias, not the canonical name. cryptobyte's
`asn1.go` imports `encoding/asn1` as `encoding_asn1` — because the sibling vendored subpackage
`.../cryptobyte/asn1` already claims the canonical `asn1` — so a `*asn1.BitString` parameter must
emit `ж<encoding_asn1.BitString>`. `getAliasQualifiedTypeName` had rendered the canonical `asn1.BitString`
(`importQualifier(pkg.Name())`), which the file's `using asn1 = …cryptobyte.asn1_package` resolves
to the *subpackage* (no `BitString`) — CS0426, and the RecvGenerator faithfully propagated the
wrong qualifier into its `.g.cs`. A `types.Type` carries no source alias, so a per-file
`importPathAliases` map (import path → the alias the file's `using` bound) is threaded into
`getAliasQualifiedTypeName`; a foreign type whose import path the file aliased renders through that alias. Only
**explicitly-aliased** imports populate the map — unaliased / blank / dot / Δ-collision-renamed
imports are absent and keep the `importQualifier(pkg.Name())` fallback, so nothing else changes
(value references were already correct — they come from the AST import name via `convIdent`; only
type references, sourced from `types.Type`, lost the alias). Cleared cryptobyte's CS0426 (which had
masked deeper `Builder.add`/`slice.Value` roots, now banked). GUARD OWED — the shape needs two
packages whose names collide so one import is forced non-canonical, not expressible in the
single-library behavioral corpus.

## Struct Type Embedding
Go structs use "[type embedding](https://go101.org/article/type-embedding.html)" instead of inheritance. Since converted structs are C# `struct`s (no inheritance), the `TypeGenerator` manages the equivalent: it adds a field for the embedded type and promotes the embedded type's fields and methods (selection shorthand). Both field and method promotion are **transitive through every embedding level**: when `top` embeds `mid` which embeds `inner`, `top` gets an accessor for `inner`'s field `n` (`top.n => ref mid.n`) and a forwarding receiver for `inner`'s method `describe` (`top.describe() => target.mid.describe()`), each resolving through `mid`'s own one-level promotion. The generator collects an embedded struct's members and methods recursively (following each field whose name equals its type's simple name — Go's embedding marker), with the closest declaration of a name winning, matching Go's promotion rules. **Pointer embeds promote too.** Go also embeds by pointer (`*traceBuf`), whose C# field type is `ж<traceBuf>`; its methods and fields are promoted exactly like a value embed (the field's ref-property is dereferenced — `target.traceBuf.Value.method()` — which binds the pointer-receiver method via the `[GoRecv]` `ж<T>` overload). The embedding-marker comparison dereferences the field type first, because a pointer field's simple name carries a `.Value` suffix (`traceBuf.Value`) that would never match the bare embed field name. This matters most *transitively*: `traceExpWriter` embeds `traceWriter` (value) which embeds `*traceBuf` (pointer), and `traceBuf`'s `varint`/`byte` must promote all the way up — without the deref-aware marker the nested pointer embed is skipped and the upper struct silently loses the method (CS1929). (Guarded by the `NestedEmbeddingPromotion` behavioral test for value embeds and the `PointerEmbeddingPromotion` test for one-level and two-level-transitive pointer embeds; runtime relies on the field case for `stackWorkBuf` → `stackWorkBufHdr` → `workbufhdr.nobj` and the pointer case for the trace writers.) Because the promotion is performed at conversion time by the generator, methods added later in hand-written C# are not automatically promoted; keeping the source in Go and re-converting (or using explicit interfaces) is the maintainable path.

**Zero values of promoted-embed structs construct through a generated constructor — never `default`.** The generator stores each promoted embed in a `private readonly ж<T>` box that only the type's constructors allocate, so a `default`-valued instance has null boxes and the first promoted-member access throws `NullReferenceException`. Both halves close this: the **converter** renders every *uninitialized* declaration of such a struct through the NilType constructor instead of `default!` — `var s shadowed` emits `shadowed s = new(nil);`, an uninitialized package-level `var g shadowed` emits `internal static shadowed g = new(nil);` (the addressed-global box wraps the same, `new(new shadowed(nil))`), and a named result `(r shadowed)` declares `shadowed r = new(nil);` — while the **generator** allocates the boxes in the *parameterless* constructor too, so the `new S()` zero values materialized by `heap(new S(), out var Ꮡs)` (an address-taken local) and golib's `@new<T>()` (`p := new(shadowed)`, which constructs via `Activator.CreateInstance<T>()`) are equally usable. The detection (`structHasPromotedEmbeds`, `visitStructType.go`) mirrors the embedded-field emission: an embed takes the promoted-box path unless it is a same-package interface, a builtin non-named embed (`int`), or a pointer to a non-named type; a cross-package embed (selector type) always promotes. Residual gap: an instance materialized as `default(T)` *outside* a declaration — a missing-key map read, a freshly `make`d slice's elements — still has null boxes; golib cannot run a constructor generically there. (Guarded by the `NamedTypeOverStruct` behavioral test — `var s shadowed` with explicit `s.ctxt.fn` and promoted `s.fn` access, plus `new(shadowed)`, vs Go.)

**A C#-keyword-named embed composes generated names from the unescaped member name.** A Go struct named for a C# keyword (`type base struct{…}`) is emitted with the `@` escape (`@base`), and embedding it makes `@base` the member name. Standalone identifier positions keep the escape (the `partial ref @base @base` accessor, the constructor parameter, member accesses like `instance.@base.id`), but every *composed* generated name must strip it, because `@` is only valid leading an identifier: the promoted-struct box field and its constructor assignments emit `Ꮡʗbase` (`Ꮡʗ@base` is CS1002), matching the already-stripped `Ꮡ`-prefixed field-reference statics and the converter's `structFieldBoxName`. (Guarded by the `NamedTypeOverStruct` extension — a keyword-named embed with promoted field/method access, a keyword-keyed composite literal, and a write through `&p.id` promoted through the embed, all vs Go.)

**Cross-package embeds resolve through the semantic model.** The member-collection above resolves the embedded struct's *syntax* (`GetStructDeclaration`) — same-package or via `CompilationReference`s. In a real [MSBuild](Glossary.md#msbuild) build, project references arrive as **metadata** references (never `CompilationReference`), so a cross-package embed — `type rtype struct { *abi.Type }` (runtime `type.go`) or a user package embedding a library struct — silently promoted **nothing**: the generated "Promoted Struct Field Accessors" section was empty and every `t.TFlag`/`t.Str`/`t.Kind_` was CS1061. The field collection now falls back to the **type's metadata symbol** (`GetTypeByMetadataName` on the normalized nested name, e.g. `go.internal.abi_package+Type`) and enumerates its public instance fields; the emitted accessors are unchanged in form — true refs through the embed (`public ref abi.TFlag TFlag => ref Type.Value.TFlag;` for a pointer embed), so writes through a promoted name reach the embedded target. Transitive promotion through a *metadata* type's own embeds is not chased (no corpus site needs it). **Promoted POINTER-RECEIVER method calls through a cross-package *pointer* embed are routed at the call site**: the generator emits no method forwarder for a metadata embed (method promotion is syntax-resolved), so `t.Uncommon()` on `Δrtype` (embeds `*abi.Type`, runtime `type.go`) was CS1929; the converter now emits the explicit hop through the embed field's box — `t.Type.Value.Uncommon()` — where the deref'd `.Value` is a ref return, binding the `[GoRecv] ref` extension addressably. A *same-package* pointer embed keeps its generated forwarder (no churn), and a promoted **value-receiver** method call (`p.Hot()`) remains a documented open gap — call through the embed explicitly. (Guarded by the `CrossPkgUser` Phase-4b extension — a promoted pointer-receiver `Calibrate` through the cross-assembly pointer embed, write-through observed via the target.) (Guarded by the `CrossPkgUser` Phase-4 extension — pointer-embed and value-embed field promotion across the assembly boundary, write-through observed via the embedded target, vs Go; cleared runtime `type.go`'s 4 CS1061, 68 → 64.)

Two refinements complete the cross-package pointer-embed story (2026-07-03, internal/reflectlite's last 4): **(a) the hop names the FIELD, which is struct-scoped** — an embed field named like a Δ-renamed package type (rtype's embedded `Type` vs reflectlite's `Type` interface, Δ-renamed `ΔType` by its type-vs-method collision) is *declared* unrenamed, so the hop emission must not apply the package-level rename (`t.ΔType.Value.Uncommon()` was CS1061); both hop arms now route through `structFieldBoxName`, the same struct-scoped naming the box accessors use. **(b) A generated interface implementation forwards through the hop too**: when an interface member has NO direct struct method and is satisfied purely by Go promotion through a single embedded-pointer field (`GoImplement<rtype, ΔType>` — `Size`/`Kind` live on `*abi.Type`), the `InterfaceImplTemplate` emits `this.Type.Value.Size()` instead of the unbindable `this.Size()` (CS1929); the `IжAdapter` template forwards the same members `m_box.Value.Type.Value.M()`. Detection is syntax-level — the converter's embed marker is the `public partial ref ж<X> F {{ get; }}` property (`GetEmbeddedPointerHopNames`) — and originally gated to a SINGLE hop, on the reasoning that multi-embed interface satisfaction was rare. The corpus surfaced one (jsonrpc's `pipe`), and the gate is gone: several embeds now route each member to the unique embed declaring it (see [*With SEVERAL embedded pointers the hop is chosen per member, not per struct*](#with-several-embedded-pointers-the-hop-is-chosen-per-member-not-per-struct)). (Guarded by the `CrossPkgUser` Phase-5 extension — a local Δ-renamed `Meter` interface colliding with the embed field name, satisfied purely by promotion through `*CrossPkgLib.Meter`, with all bump paths aliasing one shared object, vs Go.)

**A pointer-receiver method promoted through a VALUE embed is routed at the call site, not by a generator forwarder.** When `timeTimer` embeds `timer` *by value* and `timer` has a pointer-receiver method (`func (t *timer) modify(…)`), the generator emits **no** `modify` forwarder on `timeTimer` (a `target.timer.modify(…)` forwarder body would copy the value field, losing the write, and would not bind the `ж<timer>` overload) — so a promoted call `t.modify(…)` on a `*timeTimer` would leave the receiver as the whole `ж<timeTimer>` box, which the promoted method's ж/`[GoRecv]`-ref overload cannot bind (CS1929). The converter instead routes the promoted call through the embedded field's box, exactly as the *explicit* `t.timer.modify(…)` already renders: `t.of(timeTimer.Ꮡtimer).modify(…)` for a pointer local, `Ꮡt.of(timeTimer.Ꮡtimer).modify(…)` for a deref'd pointer parameter (the `&receiver.field` &-machinery supplies the correct box per receiver form). Because it field-refs the real embedded storage — never a `Ꮡ(copy)` — the mutation writes through. This is detected via the method's `types.Selection.Index()` having a single embedded-field hop (`[embeddedField, method]`); it is gated to a **value** embed (a *pointer* embed already yields the box as its field value and is left to the generated forwarder — taking its address would double-box to `ж<ж<T>>`), and to a single hop (deeper chains fall through).

**The pointer-interface ADAPTER projects through VALUE embeds the same way — chained.** A `GoImplement<T, Iface>(Pointer = true)` whose interface members are satisfied only by promotion through value embed(s) — dwarf's `type UintType struct { BasicType }`, `type BasicType struct { CommonType }`, `func (c *CommonType) Common()` — cannot forward `m_box.M()` (nothing binds on `ж<UintType>`, CS1929 ×18). The `ImplementGenerator` resolves each unbound interface member by walking the single-value-embed chain (syntax marker: the `public partial ref X X {{ get; }}` property whose name equals its type's simple name — `GetEmbeddedValueHopNames`; bounded to 4 hops), composing the box projection hop by hop via the TypeGenerator's static ref accessors: `m_box.of(UintType.ᏑBasicType).of(BasicType.ᏑCommonType).Common()`. At each level a direct-ж method binds on the projected box and anything else binds through its deref'd `.Value` (ref extensions bind on the ref-returning `Value`) — the same dichotomy as the pointer-embed hop. Mutations write through (the projection field-refs the real embedded storage in the receiver box). (Guarded by `StructPointerPromotionWithInterface`'s `counterKind → kindBase → meta` chain: `st.Stamp()` twice through the interface, `Hits()` reading the count mutated through the same boxes, vs Go.)

**A FOREIGN value embed's direct-ж method binds through METADATA.** When the embedded type lives in another assembly — database/sql's `driverConn` value-embeds `sync.Mutex`, cast `*driverConn → sync.Locker` — its direct-ж method (`Lock`/`Unlock`, emitted by the converter as `this ж<Mutex>` extensions) is visible only in the compiled sync assembly's METADATA, never this compilation's syntax trees. The syntax-based box scan (`GetBoxReceiverMethodNames`) therefore misses it and the chain-walk fell through to the unbindable `m_box.Lock()` (CS1929 ×2). The walk now also resolves the embed field's TYPE SYMBOL and probes its containing package class's static `this ж<T>` members via metadata (`GetForeignBoxReceiverMethodNames`, mirroring the foreignStruct arm's boxBound scan — only a PUBLIC ж-extension binds cross-assembly, since unexported `RecvGenerator` twins are internal); when found it forwards the box hop `m_box.of(driverConn.ᏑMutex).Lock()`, exactly the converter's own call-site form (`Ꮡdc.of(driverConn.ᏑMutex).Lock()`). The `.Lock()` resolves in the generated adapter because `sync_package` sits in the enclosing `go` namespace — the same reason the converter's own call sites bind without a `using static`. (No single-baseline behavioral guard expresses this — it needs a foreign package's ж-method type value-embedded AND implementing that package's interface, the `sync.Mutex`+`sync.Locker` shape — so **GUARD OWED**; verified by a minimal two-assembly reproduction of that exact shape, 2×CS1929 → 0.)

The **exception is the enclosing method's own `[GoRecv] ref` receiver**: a non-direct-ж pointer-receiver method renders `this ref T recv` with **no box** (`Ꮡrecv` exists only for direct-ж), so the box descent referenced a nonexistent name (CS0103 — runtime `mgcscavenge.go`, `(*scavChunkData).alloc/free` calling the promoted `sc.setEmpty()`/`setNonEmpty()` from the embedded `scavChunkFlags`). No box is needed either: the embedded field of a `ref` receiver is *addressable*, so the promoted method's `[GoRecv] ref` overload binds on the **explicit field call** — `sc.scavChunkFlags.setEmpty()` — with faithful write-through. (A *direct-ж* target on the bare receiver would have promoted the enclosing method via the capture-mode fixpoint, so this arm's target always has the `ref` overload.) The receiver name-match is guarded **rendered==raw**: an inner binding that shadows the receiver name is Δ-renamed by the shadow pass, declines the arm, and keeps the descent — the same hardening applied in `convUnaryExpr`'s `&recv.field` branch, where a pointer *local* shadowing the receiver name previously took the receiver arm and emitted `Ꮡ`+raw (a nonexistent box) instead of falling to the pointer-variable arm (`cΔ1.of(chunk.Ꮡflags)`). The fix also pre-cleared the same latent shape in `archive/zip` (`f.FileHeader.hasDataDescriptor()`), `go/internal/gcimporter`, `go/types`, and `image` (whole-stdlib reconvert diff: exactly those sites changed, nothing else). (Guarded by the `EmbeddedValuePointerMethod` behavioral test — value embed + mutating pointer-receiver methods called via a pointer local, a deref'd param, AND the enclosing `[GoRecv] ref` receiver, plus a shadowing-pointer-local control, all with write-through verified against Go; runtime relies on it for `timeTimer`'s `modify`/`stop`/`reset` and `scavChunkData`'s `setEmpty`/`setNonEmpty`.)

**A POINTER embed's BOX-receiver primary promotes through the box hop, not the deref'd value.** The promoted-receiver harvest (`GetExtensionMethods` → `IsExtensionMethodForStruct`) matched only VALUE-receiver forms (`T`/`ref T`/…), so a **direct-ж** primary (`this ж<T>`, emitted when a method takes the address of a receiver field) on an embedded type had no promoted forwarder — sha3's `cshakeState` embeds `*state`, whose `Write` is `this ж<state>`, so `Ꮡc.Write(…)` was CS1929. Such a method IS promotable through a **pointer** embed: the converter renders the hop `target.<embed>` as a `ж<T>`, so the forwarder `target.<embed>.Write(…)` binds the box receiver directly (no box construction). The `TypeGenerator` now collects those box primaries separately (`GetBoxReceiverExtensionMethods`, keyed off `GetEmbeddedPointerHopNames` so it fires ONLY for pointer embeds — a value embed's `target.<embed>` is a value that cannot bind a ж-receiver, which would need the box-hop form the sibling `GoImplement` adapter uses above) and marks each `MethodInfo.IsBoxRecv`, so the emission drops the `.Value` a value-receiver forwarder appends (`target.<embed>.M(…)` for a box primary vs `target.<embed>.Value.M(…)` for a value method). The pointer-receiver forwarder delegates to the value form unchanged, and the shared box means write-through reaches the real embedded storage. (Guarded by the `PointerEmbedBoxReceiver` behavioral test — `Outer` embedding `*Inner` whose `Add` takes `&n.total` (a box primary), the promoted `o.Add(…)` mutating through the shared box, output-compared vs Go. Full behavioral suite green; a whole-corpus confirmation on the real sha3 is deferred to the next census, as with the sibling foreign-embed fix.)

### A promoted field whose name equals the enclosing type is Δ-renamed

Go lets an embedded struct carry a field whose name equals the type doing the embedding —
debug/gosym's `type Func struct{ *Sym }` where `type Sym struct{ Func *Func; … }`, so `Sym.Func`
promotes onto `Func`. The generator's promoted-field accessor would then emit a `Func` member
inside struct `Func`, which C# rejects (CS0542 — a member cannot share its enclosing type's name).
The `TypeGenerator` now Δ-prefixes just that accessor's NAME when its simple name equals the
`NonGenericStructName` (the field ACCESS on the right keeps the original name), matching the
`ΔGoType`/`Δslice` collision-rename precedent:
```csharp
public ref ж<Func> ΔFunc => ref Sym.Value.Func;   // was: `Func => …`, CS0542
```
The promoted field is read on the embedded struct directly (`sym.Func = fn`), never via the outer
value, so no converter reference to the renamed accessor needs coordinating; a package that *did*
read `outerFunc.Func` would surface CS1061 in the gate (none does). Cleared debug/gosym's lone
CS0542. Guarded by `PromotedFieldNameIsType` (a `Node` embedding a `*sym` whose `Node` field
collides — accessed through the explicit embedded path, values vs Go).

### An EMBEDDED field whose derived name equals the enclosing type is Δ-renamed

The sibling of the case above, one layer earlier: an **embedded** field's member name is the
*unqualified type name* (Go spec), so it can equal the enclosing struct's own name outright.
io's `io_test.go` declares exactly that — a `bytes.Buffer` embedded in a struct called `Buffer`:

```go
// A version of bytes.Buffer without ReadFrom and WriteTo
type Buffer struct {
	bytes.Buffer
	ReaderFrom // conflicts with and hides bytes.Buffer's ReaderFrom.
	WriterTo   // conflicts with and hides bytes.Buffer's WriterTo.
}
```

The NAMED-field path in `visitStructType` had renamed such a field since net's `type file struct{
file *os.File }` (`typeCollidingFieldName`, the `Δ`/`ΔΔ` rules described under
[Type-vs-Method Name Collisions](#type-vs-method-name-collisions)), and every ACCESS
site already emitted the renamed form — `fieldCollidesWithType` compares the selector against its
enclosing named type without caring whether the field is embedded, and `structFieldBoxName` runs the
same rename for the box accessor. Only the EMBEDDED-field DECLARATION path was out of step: it
emitted `getCoreSanitizedIdentifier(goTypeName)` raw, so the declaration and its accesses disagreed
and the struct itself was CS0542:

```csharp
[GoType] partial struct Buffer {
    public partial ref bytes_package.Buffer ΔBuffer { get; }   // was: `Buffer { get; }`, CS0542
    public io_package.ReaderFrom ReaderFrom;
    public io_package.WriterTo WriterTo;
}
```

The rename is applied once, before the four embed emission forms (interface embed, the two plain-field
arms, and the `partial ref` promotion), using the same raw compare the named-field path uses (escape
and `Δ` markers stripped on both sides), so the marker doubling for a keyword-family or already-Δ-
renamed enclosing type carries over unchanged. Promotion is unaffected — the `TypeGenerator` derives
the backing box and every promoted forwarder from the DECLARED member name, so `Ꮡ` + the renamed
member is what both the generator and the converter's call sites spell (`rb.of(Buffer.ᏑΔBuffer)`).
The collision is only expressible across packages (one package cannot declare two types with the same
name), so it is absent from the single-package behavioral corpus and [CNR](Glossary.md#cnr) is
byte-identical apart from the new guard. Cleared io's test-host CS0542. Guarded by
`EmbeddedTypeNameCollision` (a `main.Buffer` embedding `inner.Buffer`, exercising BOTH halves the
rename must keep consistent — the explicit field selector `b.Buffer.Data` incl. a write-through, and
the promoted fields plus value- and pointer-receiver methods reached through it — with a composite
literal keyed by the embedded field and a `new(T)` zero value, values vs Go).

### Promoted pointer methods descend multi-hop value-embed chains
A pointer-receiver method promoted through two or more embedded VALUE structs descends hop by hop: the first hop through the `&`-machinery (box-vs-parameter distinction), then one `.of(<Owner>.<field-box>)` view per additional hop -- the `ж<T>` field views compose onto the method's receiver box (reflect's `sliceType` embeds `abi.SliceType` embeds `abi.Type`, whose `Common()` extension binds `ж<abi.Type>` -- CS1929):
```csharp
Ꮡ(rg).of(rig.ᏑDevice).of(CrossPkgLib.Device.ᏑSensor).Calibrate(3);
```
The own-receiver bare form joins the hop path (`recv.E1.E2.method(...)`); a chain broken by a pointer embed falls through unchanged. Guarded by `CrossPkgUser`.

### A nil embedded pointer is holdable and assignable — only its dereference panics

Go permits an embedded pointer to *be* nil: constructing `&Setting{name: name}` with the embedded
`*setting` unset, comparing it (`s.setting == nil`), and assigning it after construction
(`s.setting = lookup(…)` — internal/godebug's `New` → `once.Do` population shape) are all legal;
only *dereferencing through* the nil embed panics. The generator's promoted-pointer machinery
(a `ж<ж<T>>` box behind a ref accessor) previously conflated the two: the accessor resolved the box
with `.Value`, which treats a null held value as a nil-pointer dereference — so the *first touch* of
an unpopulated embed (even the legal `== nil` comparison, or the assignment that would populate it)
panicked from the accessor. Two generator changes separate holding from dereferencing
(`StructTypeTemplate`):

1. **The promoted-struct accessor resolves through `ValueSlot`** — golib's nil-check-free real slot.
   The `Ꮡʗ` box is a real constructor allocation, so resolving the accessor is a read of the *held*
   value, never a dereference *of* the box; reads and writes of the held `ж<T>` must not panic.
2. **The parameterized constructors box a nil pointer for an omitted POINTER embed** (`arg ?? new
   ж<T>(nil)`), matching what the NilType/parameterless constructors already emitted. The held value
   is then a *nil box* rather than a raw null, so a genuine deref of the nil embed panics with Go's
   `runtime error: invalid memory address or nil pointer dereference` (recoverable) instead of
   surfacing an unrecoverable `NullReferenceException`, and a nil embed compares equal regardless of
   which constructor produced it (`ж.Equals`: nil == nil). Value embeds are untouched (their member
   type is a value type — never null).

```csharp
// Promoted Struct Accessors
internal partial ref ж<setting> setting => ref Ꮡʗsetting.ValueSlot;   // was .Value — panicked on first touch

// internal ctor: an omitted pointer embed holds a nil BOX, not a raw null
internal Setting(@string tag = default!, ж<setting> setting = default!)
{
    this.tag = tag;
    Ꮡʗsetting = new ж<ж<setting>>(setting ?? new ж<setting>(nil));
}
```

Go's nil-deref panic is preserved *downstream*, where the actual dereference happens: the promoted
field/method accessors descend `setting.Value.name` / `target.setting.Value.bump()`, and that inner
`.Value` — on the held nil `ж<T>` itself — still routes through the strict panic path. (Guarded by
the `EmbeddedPointerNilAssign` behavioral test — compare-nil on a fresh instance, a recovered deref
panic through the nil embed, assignment of a nil pointer variable, post-construction population, and
aliasing through the populated embed, vs Go; each half discriminates independently — without (1) the
nil-variable assignment leg panics, without (2) the recover leg crashes with an unrecoverable NRE.)

### A field promoted through an embedded POINTER is rooted at the POINTED-TO allocation

A Go pointer's identity is the storage it names, and `f.pfd` for `type File struct{ *file }` is by
definition `f.file.pfd` — one address, whichever spelling reaches it. go2cs encodes a field reference
as **(containing allocation, field token)**, so that encoding is right only while the accessor stays
inside the allocation it was handed. A promotion that crosses a pointer does not: the generated
accessor deref'd on the right,

```csharp
// was — reaches the right storage, but describes the WRONG allocation
internal static ref FD Ꮡpfd(ref File instance) => ref instance.@file.Value.pfd;
```

so `of()` rooted the resulting pointer at the outer `ж<File>` box. Reads and writes still landed in
the real `file.pfd` (the `ref` is correct), which is why nothing looked wrong; only the *identity*
was, and it was wrong in the one way that cannot be seen locally — `&f.pfd` taken through `*File` and
`&file.pfd` taken through `*file` stopped being the same pointer.

The accessor now takes the hop **before** the reference is built, handing the inner type's own
accessor to the inner box:

```csharp
// now — the pointer is rooted where Go roots it
internal static ж<FD> Ꮡpfd(ref File instance) => instance.@file.of(global::go.os_package.file.Ꮡpfd);
```

Call sites are untouched (`Ꮡf.of(File.Ꮡpfd)` still): golib gains a `FieldPtrFunc<T, TElem>` delegate
plus matching `of`/`at` overloads, and C# picks the overload by the accessor's return type. The form
composes for a multi-level embed (`fileWithoutReadFrom` → `*File` → `*file`) because the inner
accessor may itself be this shape. **Value embeds keep the plain `ref` form** — their promoted fields
live in the enclosing allocation, so the existing rooting is already right — and so does a
**cross-package** embed, whose declaration syntax is unavailable to the generator: there the member
list comes from metadata and can surface public fields the inner declaration never had (the reflect
bridge's hand-added `abi.Type.sysType`/`arrayDims`, promoted into `runtime.rtype`), for which no inner
accessor exists to name. That fallback is fail-loud, not silent — naming a missing accessor is CS0117
at the corpus build.

Two identity fixes in `golib` sit underneath it, both in `ж<T>`. A field reference's SOURCE is now
compared by **pointer identity**, not object reference, because an `of()` chain mints a fresh
intermediate box on every access: `Ꮡo.of(Outer.Ꮡin).of(Inner.Ꮡv)` allocates a new `ж<Inner>` each time
it is evaluated, so `&o.in.v == &o.in.v` was **false at depth two** while correctly true at depth one.
`Equals`, `GetHashCode` and `PointerOrderToken` resolve the source through the chain now, the way
`ReferentObject` already did for lifetime questions.

Both defects surfaced as one symptom, and it is worth recording because nothing about that symptom
points at pointer identity: `internal/poll`'s `FD.Close` hung forever on a file whose `Read` was still
in flight. `Close` parks on `runtime_Semacquire(&fd.csema)` until the reader's `readUnlock` →
`destroy` → `runtime_Semrelease(&fd.csema)` wakes it, and those semaphores are keyed by pointer
identity. The two spellings of `&fd.csema` — `os.close`'s, reached through `ж<file>`, and `os.read`'s,
reached through `ж<File>` — landed in **different buckets**, so the release never reached the acquire.
Everything else on the path was already faithful: `syscall.CancelIoEx` really did abort the blocking
`ReadFile`, and the read really did return Go's `file already closed`. Guarded by
`PipeCloseUnblocksRead` (a goroutine blocked on a pipe read, a closer, output-compared against
`go run`) and `EmbeddedPointerFieldIdentity` (depth-2 chain equality, `map[*T]V` keying, and both
spellings of a field promoted through an embedded pointer).

## Interfaces
Go interfaces are duck-typed: a type implements an interface simply by having the methods. The converter emits each **user-defined** interface as a partial interface with a `[GoType]` attribute, and the **`ImplementGenerator`** source generator discovers which concrete types satisfy it and emits the implementing glue plus the implicit conversions. As a result, assigning a concrete value to an interface variable is direct — no reflection lookup or `.As(...)` call is needed:

```go
type Stringer interface {
    String() string
}

type point struct{ x, y int }

func (p point) String() string {
    return fmt.Sprintf("(%d, %d)", p.x, p.y)
}

func describe() Stringer {
    return point{1, 2}    // point implements Stringer -> assignable directly
}
```
```csharp
[GoType] partial interface Stringer {
    @string String();
}

[GoType] partial struct point {
    internal nint x, y;
}

[GoRecv] internal static @string String(this ref point p) {
    return fmt.Sprintf("(%d, %d)"u8, p.x, p.y);
}

internal static Stringer describe() {
    return new point(1, 2);   // implicit conversion emitted by ImplementGenerator
}
```

The well-known built-in interfaces (`error`, `fmt.Stringer`, etc.) are hand-written in `golib`/the baseline rather than `[GoType]`-generated, but concrete types implement them the same duck-typed way. (Earlier strategies used a generic `As`/reflection mechanism; that has been superseded by the compile-time source generators.)

Each discovered "concrete type implements interface" pairing is recorded as an assembly-level attribute in the package's `package_info.cs`, e.g. `[assembly: GoImplement<point, Stringer>]`, which `ImplementGenerator` consumes.

Two refinements to the recording pipeline (io's `NopCloser`/`eofReader`, 2026-07-03): **(a) the interface-inheritance prune drops only COMMON implementations, and only from the LOWER interface.** When an interface embeds others (`ReadCloser` = `Reader` + `Closer`), a type recorded on both the derived and an embedded interface needs only the derived pair — C# interface inheritance covers the embedded one. The prune intersects the two sets and removes the overlap from the *embedded* interface's set; it previously intersected **in place on the derived set** (the HashSet mutates its receiver), which *emptied* the derived interface's recordings whenever the overlap was empty — `GoImplement<nopCloser, ReadCloser>` vanished and the `return nopCloser{r}` failed CS0029. **(b) An INDEX-expression assignment target records against its ELEMENT type.** `mr.readers[0] = eofReader{}` assigns to a `[]Reader` element; the interface-detection previously tested the container's root identifier (`mr` — never an interface; Go forbids indexing one), so no pair was recorded and the concrete literal emitted bare. The check now types the whole index expression, and the conversion-recording path keeps the element type rather than redirecting to the container. (Guarded by the `InterfaceCasting` extensions — `rdCloser`, an inheriting interface returned concretely while the embedded `rdr` has its own recording, plus an interface-slice element assignment.)

The prune matches a FOREIGN base by its **canonical name**: the inheritance tracking stores both the alias render the declaration emits (`fs.FileInfo`) and the `getFullyQualifiedTypeName` render (`go.io.fs_package.FileInfo`) — the implementation-map keys are canonical, so the alias form alone never matched a foreign embed and both the derived and base impls emitted the same explicit members (zip's `headerFileInfo : fileInfoDirEntry` + `fs.FileInfo`, CS8646 ×6/CS0111 ×2). Structural bases track their canonical names the same way. (Guarded by `CrossPkgUser`'s `stamped` — a local interface embedding the foreign `CrossPkgLib.Labeled`, with `seal` recorded against both; only the derived record survives.)

**An embedded INTERFACE FIELD forwards the members it declares in the pointer adapter.** zip's `type nopCloser struct { io.Writer }` satisfies `io.WriteCloser` with `Write` living on the embedded interface VALUE (Go promotes the field's method set) and `Close` on the struct. The `IжAdapter` forwards still-unbound members that the field's interface declares through the field itself — `m_box.Value.Writer.Write(…)` (CS1929 with no forward). Detection is semantic (a non-static field whose name equals its interface type's simple name — the converter emits embeds as real fields), gated to a SINGLE embedded interface field, and filtered to members the field's interface (including its inherited interfaces) actually declares. (Guarded by `InterfaceCasting`'s `wrapSink{Animal}` cast by pointer to the wider `speakShutter` — both the promoted and the own member called through the interface, runtime-verified vs Go.)

**Promoted forwarders through a Δ-renamed embedded interface use the markerless FIELD name.** The converter names an embedded field after the **Go embed name**, so a struct value-embedding an interface whose C# TYPE was collision-renamed (see [Type-vs-Method Name Collisions](#type-vs-method-name-collisions)) declares `public log.slog_package.ΔHandler Handler;` — the marker lives on the type only. testing/slogtest's `type wrapper struct { slog.Handler; mod func(*slog.Record) }` (slog has both a `Handler` type and a `Logger.Handler()` method, so the type is `ΔHandler`) broke in BOTH generated wrapper forms because the `ImplementGenerator` derived the promoted-forwarder field name from the interface TYPE's simple name: the value partial struct emitted bare `ΔHandler.Enabled(…)` (CS0103 cross-package, CS0120 same-package where the bare name binds the nested interface type), and the pointer adapter emitted `m_box.Value.ΔHandler.Enabled(…)` (CS1061). The field name is now the `Δ`-stripped simple name (`GetSimpleName(…, dropCollisionPrefix: true)`, the same derivation `StructTypeTemplate` already used for embedded-field accessors) in all three places: the value template's promoted arm, the pointer arm's promoted fallback, and the pointer arm's semantic embedded-interface-field detection (which compares field name to type name and otherwise never matches `Handler` vs `ΔHandler`):

```csharp
// value partial struct — promoted members forward through the field:
public bool Enabled(nint level) => Handler.Enabled(level);
// pointer adapter — through the box value's field:
bool global::go.main_package.ΔHandler.Enabled(nint level) => m_box.Value.Handler.Enabled(level);
```

An overridden member is untouched (it forwards to the struct's own method, `this.Handle(…)` / `m_box.Value.Handle(…)`), and a non-renamed interface's simple name has no marker to strip, so every other promotion emits byte-identical code. (Guarded by `ShadowedInterfaceEmbed` — a `Handler` interface Δ-renamed by a `Logger.Handler()` method collision, value-embedded in a `wrapper` struct that overrides one of its three methods, cast to the interface by BOTH value and pointer, promoted and overridden members runtime-verified vs Go; cleared testing/slogtest's 6 errors.)

**A FOREIGN struct's adapter forwards a PROMOTED interface method through the box value, not a phantom static.** When the pointer adapter is generated in an assembly OTHER than the struct's — the struct's package class lives in a different namespace segment, so its extension methods are invisible to extension-method lookup — the `ImplementGenerator` forwards each interface member through a package-class STATIC call (`xcoff_package.ReadAt(m_box, …)`). But this only works for a method the struct **declares directly** (whose `RecvGenerator` ж/ref static exists). A **promoted** interface method — debug/buildinfo's `*xcoff.Section` → `io.ReaderAt`, where `Section` embeds the `io.ReaderAt` interface so `ReadAt` is promoted, not declared — has no such static, so the forward targets a nonexistent overload (CS1501, "no overload takes 3 arguments"). The static forward is now gated on a real box/ref-bound static existing; when absent, the adapter forwards through the box VALUE — `m_box.Value.ReadAt(…)` — invoking the struct's own PUBLIC promoted method (the same promotion `File.ReadAt` etc. rely on). A directly-declared method keeps the static forward unchanged (no churn). (Validated by the `core/debug/buildinfo` build — its `*os.File → io.ReaderAt` [direct `ReadAt`, static] and `*xcoff.Section → io.ReaderAt` [promoted, box-value] adapters — plus the full behavioral suite + tar/math-big/net corpus; a single-assembly behavioral guard cannot host it — the shape needs a struct embedding a THIRD package's interface, cast cross-assembly, so **GUARD OWED**.)

**Pointer-sourced interface values use a generated ADAPTER, not the value-boxing partial struct (2026-07-03).** A Go interface value created from a pointer — `var s Iface = &t`, `New(new(lockedSource))`, `Rand{src: &runtimeSource{}}` — holds the **pointer**: every call through the interface mutates the original object, `s.(*T)` recovers that same pointer, and interface equality is pointer identity. The old emission deref'd the box into the C# interface (`~box`, boxing a **copy**) — aliasing divergence — and could not serve **direct-ж** receiver methods at all (a method that takes the address of a receiver field is emitted with the box AS its receiver, `this ж<T>`, which a struct's `this` can never bind — math/rand `lockedSource` CS1929). The converter now records such casts as `[assembly: GoImplement<T, Iface>(Pointer = true)]`, and `ImplementGenerator` emits a sealed **adapter class** instead:

```csharp
internal sealed class runtimeSourceжSource : go.math.rand_package.Source, IжAdapter
{
    private readonly ж<runtimeSource> m_box;
    public runtimeSourceжSource(ж<runtimeSource> box) => m_box = box;
    public object? Box => m_box;
    long go.math.rand_package.Source.Int63() => m_box.Int63();  // direct-ж / ж-twin binds the box
    // Equals/GetHashCode delegate to box identity (Go pointer-interface equality)
}
```

Cast sites emit the adapter around the box (`Incrementer inc = new CounterжIncrementer(c);`, `src: new runtimeSourceжSource(Ꮡ(new runtimeSource()))`), covering call arguments, keyed composite-literal fields, and `var` declarations; a pointer-typed operand in these positions renders as the box (isPointer ident context), not the deref'd receiver ref-local. Member forwarding picks the receiver form per method: direct-ж and `[GoRecv]` ref-extensions (whose `RecvGenerator` ж-twin exists) forward to `m_box.M(...)`; plain value-receiver methods forward to `m_box.Value.M(...)` (Go copies the value at the call). The golib type-assert machinery (`_<T>()`) unwraps `IжAdapter.Box` so `s.(*T)` yields the original `ж<T>`, and `AreEqual` unwraps both operands so interface-vs-interface and interface-vs-pointer comparisons are box identity (`ж<T>.Equals` is already identity-based); `iface == ptr`/`iface != ptr` comparisons emit `AreEqual(...)` with the pointer operand kept as the box (the old `iface == ~p` deref form compared a copy). Because each adapter is a distinct class, the interface-inheritance de-duplication (dropping `GoImplement<T, Source>` when `GoImplement<T, Source64>` exists and `Source64` embeds `Source`) exempts pointer-form pairs — a `Source`-targeted cast site references `runtimeSourceжSource` even though `runtimeSourceжSource64` also implements `Source`. VALUE-sourced casts (`var s Iface = t`) keep the partial-struct implementation — Go copies the value into the interface there, which is exactly C#'s struct-boxing semantic. Known limits (documented, not yet needed by the corpus): a cross-package pointer cast keeps the old deref-copy form (the adapter class only exists in the impl type's assembly — `isLocalImplType` gate), and asserting an adapter-held interface to a *different* interface (`s.(Source64)` on a `Source`-created value) is not yet unwrapped. (Guarded by the `InterfaceCasting` extension — pointer-receiver `Counter` with a direct-ж member cast to an interface, mutations verified through BOTH the interface and the original pointer, assert-back recovering the same box, and `back == c` pointer equality, run-verified vs Go; and by `InterfaceImplementation`'s output comparison — `zoo[0] == f` interface-vs-pointer identity.)


**Non-empty interface-to-interface conversions use a forwarding adapter.** A Go interface value may be assigned or passed to another non-empty interface when the source interface method set satisfies the target (`var local localLabel = foreign`, where `foreign` is `CrossPkgLib.Labeled`). C# has no structural conversion between unrelated interfaces, so the converter records the interface pair as `[assembly: GoImplement<CrossPkgLib_package.Labeled, localLabel>]` and emits the cast site as a generated adapter:

```csharp
CrossPkgLib.Labeled foreign = new CrossPkgLib.Sensor(Name: "adapter"u8, Temp: 21);
localLabel local = new CrossPkgLib_LabeledᴠlocalLabel(foreign);
fmt.Println(labelOf(new CrossPkgLib_LabeledᴠlocalLabel(foreign)));
```

`ImplementGenerator` emits a sealed adapter implementing the target interface and `IInterfaceAdapter`, stores the source interface value, and forwards each target member to that value. The golib assertion/equality helpers unwrap `IInterfaceAdapter.Value` before type assertions, `Implements<TInterface>`, and `AreEqual`, so the wrapper behaves as an interface view over the original Go interface value rather than a new concrete payload. Guarded by `InterfaceToInterfaceAdapter`, which imports `CrossPkgLib.Labeled`, assigns it to a local compatible interface, passes it as a parameter, and output-compares the forwarded calls.

Two rules govern how concrete implementation records are emitted:

* **Only impl types declared in the *current* package are recorded.** `ImplementGenerator` realizes the attribute by emitting a `partial struct <Impl> : <Interface>` into the **current package's** namespace and class — so it can only add an interface to a type defined in the *same assembly*. A pairing whose impl type is *imported* from another package (e.g. `image/color/palette` building `[]color.Color{ color.RGBA{…} }`) is therefore **not** re-emitted in the consumer: that relationship is already established in the impl type's own package (`image/color` records `[assembly: GoImplement<ΔRGBA, Color>]`). Re-emitting it in a consumer would generate a broken cross-assembly partial (a fresh empty `palette_package.ΔRGBA` rather than the real `color_package.ΔRGBA`), so the converter skips any pairing whose impl type is not local.
* **Multi-segment interface references are root-qualified.** The `GoImplement` attributes are emitted before the file's `namespace` with only `using go;` in scope; that directive imports the *types* of namespace `go` (so a top-level `io_package.Writer` resolves unqualified) but **not** its nested namespaces. A multi-segment package class such as `container.heap_package.Interface` is therefore root-qualified to `go.container.heap_package.Interface` so it resolves; single-segment refs (`io_package`, `sort_package`) are left unchanged.

### Every eligible interface carries runtime duck-typing shells — the sole resolver of a structural assert

The `GoImplement` STRUCTURAL recorders were a compile-time *approximation* of Go's structural satisfaction, incomplete **by construction** — which is why, once these shells existed, they were retired outright (2026-07-25; see the RETIRED note under *Multi-Result Values and Comma-Ok Forms*). A dynamic type may live in a package converted **after** the interface's own, and then no record can exist: a dynamic type may live in a package converted **after** the interface's own, and then no record can exist. `io/fs` is converted before `os`, so `fs/package_info.cs` records only `subFS→ReadDirFS` — `os.dirFS` is unreachable — and every `fsys.(ReadDirFS)` against an `os.DirFS(…)` value silently missed. golib's structural probe answered the *question* correctly (`StructurallyImplements`) but had nothing to *construct*, so the assertion still failed. That is what kept io/fs at 16/18 (`Glob` returning nothing, `WalkDir` seeing only the root).

With no dynamic code generation available (Native AOT), a **per-interface compile-time artifact is the irreducible minimum**, and it must live in the **interface's own package class** — the only placement guaranteed loaded at every asserting site, and the only one that yields a single cross-assembly identity. `TypeGenerator` therefore emits, for every non-generic, non-constraint, non-empty `[GoType]` interface, **two sibling shells**, discovered through a new `[GoInterfaceShell]` stamp on the interface itself. No static member is added to the interface — that shape would be inherited by every embedding interface, which is both a large CS0108 hiding class and a method-set corruption (no Go type can implement a static helper), and it makes the shell NAMES non-contractual so the generator may disambiguate freely:

```csharp
[global::go.GoInterfaceShell(typeof(ΔSpeaker<>), typeof(ΔSpeakerᴛObj), "Speak")]
public partial interface Speaker
{
}
```

**Tier 1 — `ΔI<ᴛTTarget>`, delegate-bound, for a REFERENCE-typed dynamic value** (every `ж<X>` receiver box, i.e. every pointer-sourced Go interface value — the dominant case). Each interface method is a *pre-bound delegate*, so a forwarded call costs a delegate invocation, not a reflective one; that matters because a wrapper is obtained once and then called possibly millions of times (a duck-typed `io.Reader` inside `io.Copy`, a wrapped `sort.Interface`'s `Less`/`Swap`). It closes over the **element** type — the pointee — because that is what makes *both* receiver forms bindable, and the dispatch encodes Go's `*T` method-set rule directly:

```csharp
internal sealed class ΔSpeaker<ΔTTarget> : Speaker, IInterfaceAdapter
{
    private delegate global::go.@string SpeakByPtr(ж<ΔTTarget> targetʗ);
    private delegate global::go.@string SpeakByVal(ΔTTarget targetʗ);

    private static readonly SpeakByPtr? s_SpeakByPtr;
    private static readonly SpeakByVal? s_SpeakByVal;

    public global::go.@string Speak()
    {
        if (m_targetᴛ_is_ptr && s_SpeakByPtr is not null)
            return s_SpeakByPtr(m_targetᴛ_ptr!);
        else
            return s_SpeakByVal!(m_targetᴛ_is_ptr ? m_targetᴛ_ptr!.Value : m_targetᴛ);
    }

    static ΔSpeaker()
    {
        global::go.AdapterBinder.ResolveReceiverMethods(typeof(ΔTTarget), "Speak", out byPtrᴛ, out byValᴛ);
        s_SpeakByPtr = byPtrᴛ is null ? null : byPtrᴛ.CreateStaticDelegate(typeof(SpeakByPtr)) as SpeakByPtr;
        s_SpeakByVal = byValᴛ is null ? null : byValᴛ.CreateStaticDelegate(typeof(SpeakByVal)) as SpeakByVal;
        …
        ᴛBoundByPtr = boundByPtrᴛ;      // read by the binder BEFORE the shell is handed out
        ᴛBoundByVal = boundByValᴛ;
    }
}
```

**Tier 2 — `ΔIᴛObj`, reflective, for a VALUE-typed dynamic value** (the forcing case: `os.dirFS` is `[GoType("@string")] partial struct dirFS`, a value type held in an `fs.FS`). It holds the value as `object` and forwards through `MethodInvoker`s resolved once per (dynamic type, interface) pair, so it needs **no generic instantiation at all**. That is not a stylistic choice: under Native AOT `ilc` roots exactly the instantiations visible in source, and a `MakeGenericType` driven by a run-time `GetType()` over a value type is never one of them, so this is the tier that is unconditionally available. It is emitted only when every member survives the `object` round-trip (a Go variadic tail lowers to `params Span<T>`, a ref-struct that cannot be boxed):

**The forwarder dispatches on ARITY (2026-07-26).** `GoShellBinding.Invoke` used to build a fresh `object?[args.Length + 1]` on every forwarded call, purely to prepend the receiver — 32 B allocated and zeroed per call even for a Go method with no parameters at all, which is the common case (`Len`, `Error`, `String`, `Less`). The bound members are static extension methods, so `MethodInvoker`'s `obj` is always `null` and the receiver occupies the first *argument* slot; the BCL's fixed-arity overloads take up to four arguments, so Go arities **0–3** now forward with no array and arity 4+ keeps the `Span` path. Measured on `PerfIfaceShell` (one object-tier call per iteration, provisional): JIT **633.7 → 588.0 ms**, Native AOT **760.1 → 727.8 ms** — the AOT column matters more in principle, because the binder's belt degrades *both* shell tiers to this one there, so the cost is paid twice per iteration rather than once. The boxed *return* is not fixable this way: `MethodInvoker` returns `object?` and the shell unboxes, and removing that needs a non-reflective forwarder, which needs a generic instantiation — exactly what this tier exists to avoid. (Guarded by the `ShellForwardArity` behavioral test: one anonymous interface spanning arities 0–5 plus an int-returning and a mixed-parameter shape, every method folding its arguments into the printed result so a dropped, duplicated or reordered argument diverges from `go run` instead of passing silently. An instrumented run confirms all four fixed arms *and* the `Span` fallback are reached.)

```csharp
internal sealed class ΔSpeakerᴛObj : Speaker, IInterfaceAdapter
{
    object? IInterfaceAdapter.Value => m_targetᴛ;

    public global::go.@string Speak() => (global::go.@string)m_bindingᴛ.Invoke(0, m_targetᴛ)!;
}
```

The tier choice branches on `Type.IsValueType`, and **either tier belts to the other** when construction fails — AOT rooting is source-shape sensitive, so "we never reach that instantiation" cannot be asserted, only constructed. (Honest limit: because tier 1 closes over the *pointee*, and a pointee is usually a struct, the pointer tier is AOT-**graceful** rather than AOT-guaranteed — an unavailable instantiation degrades to the object shell, not to a miss.)

**One binder owns the method-set discipline.** golib's `AdapterBinder` resolves every binding through `TypeExtensions.GetGoMethodSetCandidates(element, isPointer)` — the *same* receiver rule `StructurallyImplements` applies, factored out for exactly this reason. So a value-sourced shell binds only value-receiver methods and can never widen a Go method set: `PtrOnly{}` (whose `Speak` has a pointer receiver) MISSES, `&PtrOnly{}` matches, and a pointer source calling a value-receiver method dereferences the box **per call**, matching Go's copy-at-the-call. This also fixes a latent over-broad lookup: `GetExtensionMethod` collapses a closed `ж<X>` to the open `ж<>` definition (correct for single-dispatch precedence, wrong for a method-set query), so a name shared across types could bind the wrong receiver; the binder matches on element identity instead.

**A candidate's EMITTED name is not always its GO name — the method set must be read in Go names (2026-08-01).** A Go method set is a Go-level fact reconstructed at run time from emitted C#, and the emitted name can carry the converter's collision-avoidance marker instead of the Go name. A `-tests` variant Δ-renames a test-file method declarator whose bare name would hijack a same-named dot-imported function at every unqualified call site — B9 in `performNameCollisionAnalysis`, and a real compile fix, since C# resolves the enclosing class's method group ahead of any `using static` import. `io`'s `multi_test.go` is exactly that shape: `func (c *writeStringChecker) WriteString(string)` against the dot-imported `io.WriteString`, so the method emits as `ΔWriteString` while `io.StringWriter`'s member keeps the bare `WriteString`. `StructurallyImplements` compared the two emitted names, answered MISS, no shell was built, and `MultiWriter`'s `w.(StringWriter)` fell through to the `Write` leg — which returns the same `(n, err)`, so the divergence was **silent**: only Go's own `TestMultiWriter_StringCheckCall`, which asserts that `WriteString` was *called*, could see it. `TypeExtensions.GoMethodNameMatches` now projects a candidate's leading `ShadowVarMarker` away, the same way `GoReflect`'s type naming (`ΔHandle` → `Handle`) and struct-field projection already recover a Go name from an emitted one. It runs as a **second pass**, after an exact-name pass satisfies nothing, so a Δ-renamed candidate can never displace a plainly-named one; `AdapterBinder.ResolveReceiverMethods` applies the identical two-pass rule, because a binder that bound a method the probe would not have counted is precisely the disagreement `GetGoMethodSetCandidates` exists to prevent. Measured: `io` 47 → 48 of 54.

**`TryCreate` is FAIL-SOFT.** The structural probe is deliberately NAME-ONLY for an open-generic receiver method — a Go `gbox[int]` whose `Get() T` returns an `int` matches `interface{ Get() string }` under that weaker rule (measured). Go answers `ok=false` there, so a shell that cannot be built answers `false` rather than escaping as an exception; a false-positive match must reproduce today's harmless miss, never a crash.

**Tier order in `builtin.TryTypeAssert` is unchanged, and the shells fire only where it previously answered MISS**: nominal `case T` → `IжAdapter` unwrap → `AdapterRegistry` (compile-time adapters) → **shell memo** → `Implements<T>` gate → **shell construction**. That is the regression floor — no assertion that already resolved can take a different path. Construction costs on the order of a microsecond and `fmt` probes three interfaces per formatted value, so the memo ships in the same change: `AdapterRegistry` caches the (dynamic type, interface) decision, **including the negative**. It is deliberately **not** cleared on `AssemblyLoad` — Go fixes a type's method set at compile time, and the extension methods carrying it live in that type's own assembly, which is loaded by definition when we are holding an instance of it. (Extension-method *discovery* caches are still invalidated on load; only this decision cache is not.)

**The two memoized tiers were folded into ONE per-interface itab cache (2026-07-26).** The ladder above consulted the nominal `AdapterRegistry` (a `(Type, Type)` tuple hash) and then a separate per-interface shell memo, in sequence, on *every* assert — and for a shell-resolved pair the first of those could never hit, by construction: a hit would have returned before the shell tier was ever reached. Together with a `target.GetType()` whose result was dead for an interface target (its only consumers sat behind a `typeof(T).IsValueType &&` that short-circuits false), a memoized assert cost **three `GetType()` calls, two dictionary lookups and two `RuntimeType` property calls** before it did any work. `builtin.Itab<TInterface>` replaces both reads with one: a single entry per (dynamic type, interface), holding the resolver **whichever tier produced it** — a registered nominal adapter factory, a runtime shell factory, or `null` for a decided MISS — exactly as Go keeps one `*itab` per pair. `AdapterRegistry` is unchanged as the authoritative durable record; the itab is a projection formed by reading it back, never by forming a second decision. `IsInterface`/`IsValueType` are hoisted to per-closed-generic statics, and a **monomorphic slot** sits in front of the dictionary (an assert site is overwhelmingly single-typed, the locality assumption Go's per-site checks rely on), so the steady-state read is a static field load, an int compare and a reference compare. Tier precedence is unchanged — nominal still wins, shells still fire only where it answers MISS.

Two correctness points are load-bearing. **The entry is one immutable object**, never two static fields: a torn `(type, resolver)` pair would silently construct the *wrong* implementation. And unifying the caches reintroduces the hazard the two separate dictionaries avoided by construction — a shell (or a decided miss) memoized *before* a lazily-loaded assembly's module initializer registers the nominal adapter for that same pair — so `AdapterRegistry.Register` bumps an **epoch** (only on a `TryAdd` that actually adds; the registry is first-wins, so a duplicate can invalidate nothing) and every itab entry carries the epoch it was decided under. An entry from an older epoch is simply never matched and is overwritten when its pair is next formed, which needs no clearing step that could race a concurrent fill. Registration is startup-time and append-only, so the steady state never re-forms anything. A side effect of recording the miss as `null` rather than reading a factory back is that a **miss is now stable**: previously a pair whose factory exists but throws its binding failure out of construction answered `false` once and then re-threw on the next assert, because the projection had published that factory.

Measured on `PerfIfaceShell` (5M iterations, each two asserts plus two forwarded calls; provisional, taken on a contended machine): JIT **789.8 → 683.1 ms** for the unified cache alone, **→ 633.7 ms** with the monomorphic slot — 158.0 → 136.6 → 126.7 ns per iteration. Native AOT **976.5 → 866.2 → 760.1 ms**, a larger relative win because under AOT both shell tiers are reflective and dictionary work is a bigger share. (Guarded by the `ItabLateRegistration` behavioral test — shells decided first, a real module initializer registering real generated adapters afterwards, every pair re-formed, misses still misses, two interfaces over the same types kept separate — and by `tests/GolibTests/ItabEpochTests`, which pins what Go cannot express: tier precedence is deliberately *invisible* from Go, since a shell and an adapter forward to the same receiver methods, so the epoch's effect is only observable at the golib level, where a memoized MISS must start succeeding once an adapter is registered for it.)

Guarded by three behavioral projects, each pairing a `main` package with a sibling library so the interface's package genuinely cannot see the concrete types (the io/fs shape): **`NamedInterfacePointerMethodSet`** (the X3 negative — a value of a pointer-receiver-only type must MISS — plus the positives, a wrong-signature miss and the fail-soft generic-receiver miss), **`NamedInterfaceLateAssert`** (a value-typed defined string satisfying a THREE-DEEP embedded interface chain cross-assembly, an unexported interface, and a partial implementer that must not satisfy the derived interface), and **`NamedInterfaceAdapterIdentity`** (`%T`, re-assert, type switch and interface equality through a shell). Counter-proven: with shell emission disabled, all nine `NamedInterfaceLateAssert` assertions revert to MISS.

**The ladder's CONCRETE-target miss read a custom attribute per call (fixed 2026-07-26) — this was the whole of the `Iface` benchmark row.** Everything above tunes the INTERFACE-target tiers. The concrete-target tiers below them ended in one that answers Go's rule that only dynamic (anonymous) struct types convert to each other, and it asked that question with `Type.IsDynamicType()` — whose body is an uncached `GetCustomAttribute<GoTypeAttribute>()` that materializes a fresh attribute instance on every call. Measured against live golib: **785.92 ns and 368 bytes per call on the JIT, 3,826.27 ns and 2,017 bytes under Native AOT** (ILC parses the attribute blob out of the compiled image's metadata each time and has no equivalent of the JIT's attribute caching). Every `v, ok := x.(SomeStruct)` that does *not* match reached it, so the ordinary named-struct assert — the most common assert in Go code — paid it corpus-wide, and it is what made `PerfIface` the worst row in the performance table at **158× Go on the JIT and 660× under AOT**, a benchmark whose asserts miss on 4 of every 6 iterations. Two layers, one root cause: `IsDynamicType` now **memoizes per type**, beside `GetStructFieldNames`, which memoizes for exactly this reason (deliberately *not* added to `ClearTypeCaches` — like the field-name cache, an assembly load cannot change a type's own attributes); and `AssertFacts<T>` gains **`IsDynamic`**, joining the `IsInterface`/`IsValueType` per-closed-generic facts already hoisted there, so a named-struct miss short-circuits before `GetType()` is even called. Reordering pure predicates in the `&&` chain preserves semantics exactly, and `AnonymousStructs` pins both directions — an anonymous struct asserted against an identically-shaped anonymous struct must HIT, and against a named struct with the same fields must MISS.

**One marker probe answers "is this a wrapper?" for both adapter tiers.** With the attribute lookup gone, what remained on the common path was **four failing interface type tests per type-switch-plus-assert iteration**: `builtin.type()` probes `IInterfaceAdapter` then `IжAdapter`, and `TryTypeAssert` probes the same two. That is not free, and the asymmetry is the runtime's, not the emission's — measured on a boxed value implementing neither, **one failing interface test costs ~2.9 ns (JIT) where one failing sealed-CLASS test is below measurement noise**, because a failing interface `isinst` walks the type's interface map while a failing class test is a short parent-chain compare the JIT inlines. Both consumers ask the same question first — *is this object standing in for another value?* — and for an ordinary Go value the answer is no, so it is worth exactly one test. `IжAdapter` and `IInterfaceAdapter` now share an empty base marker, **`IGoAdapter`**, and both call sites probe it once to gate their two tiers. Generated adapters implement the base transitively, so **`go2cs-gen` is unchanged and no emitted C# moves**. Ordering is preserved where it is load-bearing: in `TryTypeAssert` the `IжAdapter` match leaves the `switch` but stays BELOW the `string` and `case T` arms (an adapter that is itself a `T` still resolves as that `T`), and the gate flag is re-read after each unwrap since what an adapter yields need not be one itself; in `builtin.type()` the `string` arm moves after the `IжAdapter` arm, which cannot change an answer because `string` is sealed and implements neither marker.

Measured end-to-end on `PerfIface` (20M iterations of one slice-of-interface read, two interface dispatches, one concrete comma-ok assert and a three-case type switch). The per-stage A/B is a `--filter PerfIface` run: JIT **10,117.8 ms (158.24×) → 458.1 ms (7.20×)** for the attribute fix, **→ 379.7 ms (5.95×)** with the marker probe. The **published figure is the full-table quiet-machine run, median of 5: JIT 370.1 ms (5.86×) and Native AOT 262.3 ms (4.15×)**, against the pre-fix 10,117.8 ms (158.24×) and 42,228.2 ms (660.42×) — a 27× and 161× improvement respectively. Peak working set fell 41.3 → 23.1 MB (JIT) and 29.6 → 11.1 MB (AOT) as ~4.9 GB of per-assert attribute garbage stopped being allocated. A decomposition micro-benchmark against live golib (best-of-5, `DOTNET_TieredCompilation=0`) attributes the whole of it, per iteration of the emitted loop: slice element read 1.8 ns, two interface dispatches 3.9 ns, comma-ok assert **579.0 → 12.1 → 8.1 ns**, type switch **16.0 → 11.0 → 4.4 ns** — against a floor of 1.2 ns for a plain `s is Circle c` and 1.4 ns for a bare C# pattern ladder, both indistinguishable from the slice read alone. `IfaceShell` moved 44.58× → 44.13× → 40.58× across the same two changes — flat for the first (it is the oracle, and does not touch the miss tier) and improving on the second, since the shell tier enters through the same assert. **AOT now beats the JIT on this row**, reversing the pre-fix order, because ILC's failing interface type tests are markedly cheaper (3.7 ns for two against 9.2 ns on the JIT).

**ANONYMOUS (`dyn`) interfaces use the SAME shells — the second renderer is gone.** An interface literal was the *original* duck-typing case, and it had its own machinery long before named interfaces got any: `TypeGenerator` stamped two static `ᴛAs<ᴛTTarget>` conversion methods plus a `ᴛAs(object)` overload onto every `[GoType("dyn")]` interface, emitted a `Δ<Iface><ᴛTTarget>` wrapper next to it (with a full operator/nil block it never needed), and `builtin.TryTypeAssert` reached that wrapper by *reflecting for the method by name* and closing it with `MakeGenericMethod`. Two renderers of one idea, and the older one carried three real defects the shells do not:

* **It was the last unbelted `MakeGenericMethod` in the assert path.** `MakeGenericMethod` over a run-time type is dynamic code: under Native AOT it succeeds only for an instantiation `ilc` already rooted, and there was no fallback tier — an unavailable instantiation was an unrecoverable MISS, silently wrong rather than degraded. The shells' `IsValueType` branch answers the same case with a shell that needs *no* instantiation, and belts the other way when it does.
* **Its static members were on the interface.** A converted interface's statics are inherited by every interface that EMBEDS it, so `interface{ error; Temporary() bool }` forwarded `ᴛAs` overloads into its own wrapper (CS0102 ×6) and demanded a static helper from the dynamic value's Go method set — both had to be filtered out downstream. Attribute discovery removes the shape rather than the symptom.
* **Its binding used the by-name extension lookup.** `GetExtensionMethod` collapses a closed `ж<X>` to the open `ж<>` definition — right for single-dispatch precedence, wrong for a method-set query — so a method name shared across types could bind another type's receiver. `AdapterBinder` matches on element identity.

Nothing about the *emitted* dyn interface changes except the disappearance of `ᴛAs`: an interface literal is still a `[GoType("dyn")] partial interface`, still resolved structurally at run time, still fail-soft. The `dyn` key is no longer read by `TypeGenerator` at all — its only remaining reader is the runtime's `Type.IsDynamicType`, used for Go's anonymous-struct-to-anonymous-struct conversion, which reads the `[GoType]` attribute directly. Guarded by the existing dyn corpus (`AnonymousInterfaces`, `DynIfaceParamNameCollision`, `DynamicInterfaceKeywordMethod`, `AnonIfaceMethodSetWidening`, `AnonIfaceThroughPointerAdapter`, `AnonInterfaceCrossFile`, `AnonInterfaceSignatureAssert`, `DerivedInterfaceStructuralProbe`, `StructuralAssertFailSoftMiss`), which is the dyn contract and stayed green through the migration unchanged, plus the `PerfIfaceShell` performance benchmark, which executes both tiers under a Native AOT publish.

**golib's three hand-written interfaces joined the same mechanism.** `error` (golib), `fmt.Stringer` and `io.Reader` (the baseline stubs) predate the marker and expose plain `As<T>` helpers; `TryTypeAssert` found *those* by the same reflective probe and closed them the same way, so deleting the probe would have taken their duck-typing with it. Each is now stamped `[GoInterfaceShell(typeof(<I><>), null, "<M>")]` — their existing `<I><T>` carrier class **is** the delegate-bound generic shell, and always was — and each carrier's `(in T)` constructor became `(T)`, because `AdapterBinder` locates a shell's constructor by exact parameter type and an `in` parameter is `T&` in metadata. `null` for the object shell is deliberate rather than a gap: a reflective tier would have to reproduce these carriers' `%v`/`%T` formatting contract (`error<T>.ToString(format, provider)`), so a value-typed error still binds through the generic shell — AOT-graceful, exactly as before. Because a hand-written shell has no `ᴛBoundByPtr`/`ᴛBoundByVal` flags, the binder treats those as optional and forces the type initializer explicitly (`RuntimeHelpers.RunClassConstructor`), so an unbindable pair is still decided — and memoized — at factory-build time rather than rediscovered per construction.

### Cross-package pointer-to-interface conversions use the foreign adapter
A pointer-sourced cast to an interface implemented by a FOREIGN type references the foreign assembly's PUBLIC adapter class - os's `err = &PathError{...}` emits `new fs.PathErrorжerror(Ꮡ(new PathError(...)))`, io/fs having generated the adapter from its own `GoImplement<PathError, error>(Pointer = true)` record. The record's existence is read from the imported package's package_info (`parseExportedPointerImplements`, the same imported-records pattern as GoTypeAlias). The existence key is the **shared canonical spelling** both sides compose through `implementRecordKey` — `<declaring package>|<C# simple type>|<pkg>_package.<Iface>` — exactly as the value-implement records do; keeping the package CLASS on the interface side is what stops image's `Paletted→image.Image` record from satisfying a `Paletted→draw.Image` cast and referencing the adapter that implements the WRONG interface (CS1503). The reference goes through the file-local package ALIAS (`fs.PathErrorжerror`, user-ruled style) via getAliasQualifiedTypeName, which also registers the using — except when that yields a whole-TYPE alias for a collision-renamed foreign type (`imageꓸRGBA`), which is an identifier and not a path, so the base is rebuilt as the package qualifier plus the type's EMITTED simple name (`image.ΔRGBAжImage`). Guarded by `CrossPkgUser` (`rep = mtr` -> `new CrossPkgLib.MeterжReporter(mtr)`; `&CrossPkgLib.Alarm{}` -> error; and the same-simple-name LOCAL `Labeled` — `var localLb Labeled = sp2` takes the LOCAL `CrossPkgLib_SensorжLabeled`, never the lib's exported `SensorжLabeled`).

An **EXPLICIT pointer-to-interface conversion** — Go's `image.Image(dst)` with `dst *image.RGBA` (image/draw) — is the same interface cast in conversion clothing and routes through the same machinery: `isTypeConversion` probes the ORIGINAL pointer type against an interface target (the value type alone does not implement it — the elem-only probe misread the conversion as a constructor call, `new image.Image(dst)`, CS0144), and the emission re-renders the argument in its BOX form and CASTS the adapter to the interface — `((image.Image)new image_ΔRGBAжImage(Ꮡdst))` — because the adapter implements its members explicitly, and a chained member access on the conversion result (`CrossPkgLib.Labeled(sp).Label()`) cannot bind on the adapter class itself (CS1929). (Guarded by `CrossPkgUser`'s `CrossPkgLib.Labeled(sp2).Label()` / `LabeledOf(sp2)` pair, output-compared vs Go.)

### `%T` (and type-name rendering generally) unwraps generated adapters and pointer boxes

Go's `%T` prints the interface value's **dynamic Go type** — `*strings.byteReplacer`, never an implementation artifact. The managed model interposes artifacts a name renderer must see through (strings' `TestPickAlgorithm`, which `%T`s each replacer algorithm, printed `strings.byteReplacerжreplacer`):

* a **pointer-sourced ж adapter** (`byteReplacerжreplacer : IжAdapter`) stands in for the `*T` it wraps → renders `*strings.byteReplacer`;
* a **value-sourced foreign ᴠ adapter** (`typelib_Markᴠstamper`) wraps a struct copy → renders the struct type, `typelib.Mark`;
* an **interface-to-interface adapter** (`IInterfaceAdapter`) forwards to its wrapped value's dynamic type;
* a **raw receiver box** `ж<T>` (a pointer held in an `any` with no adapter in its history) renders `*main.loud`;
* a **converted named type** package-qualifies from its `<pkg>_package` declaring class: `go.main_package+soft` → `main.soft`.

The unwrap lives at the shared choke points, so every formatting path agrees: `GoReflect.GoTypeName` (which `reflect.Type.String()` serves the converted `fmt`'s `%T` from, via the Phase-1 reflection bridge) gains a `TryAdapterWrappedType` arm, and golib's `builtin.GetGoTypeName` (the stub `fmt` `%T`, the testing shim's `TestFormat`, and interface-conversion panic texts) unwraps `IжAdapter.Box`/`IInterfaceAdapter.Value` at the value level and routes ж/adapter/named types through `GoReflect.GoTypeName`. Adapter detection is structural, never name-parsed for the wrapped type: `IжAdapter` (or the `ᴠ` infix per `Symbols.ValueAdapterInfix` for value adapters) identifies the adapter, and the wrapped type is read from the adapter's single one-parameter constructor (`ж<T>` → pointer-sourced; the struct type → value-sourced). `reflect.Kind()`/`Elem()` of an adapter type still report the adapter class (a reflection-bridge follow-up owned with R5's DeepEqual work), but `%T`/`String()` — the only surface the corpus exercises — are Go-exact. (Guarded by `FormatTypeAdapters` — a two-project behavioral test whose `typelib` sub-package supplies the foreign value implementer, output-comparing `%T` over the ж adapter, the local value implementer, the raw box, a plain named struct, the ᴠ adapter, and a nil interface vs `go run`; without the unwrap it prints `main_package+loudжgreeter` / ``ж`1[[go.main_package+loud, …]]`` / `main_package+typelib_Markᴠstamper`.)

The **VALUE mirror of the explicit conversion** — `crypto.SignerOpts(sigHash)` with `sigHash crypto.Hash` (crypto/tls, CS0030 ×4) — routes a **FOREIGN named non-interface VALUE source** through the same `convertToInterfaceType` machinery, keeping the outer interface cast: `((crypto.SignerOpts)new crypto_HashᴠSignerOpts(sigHash))` plus the local value-form `GoImplement<crypto_package.Hash, crypto_package.SignerOpts>` record. A plain cast cannot bind here: a foreign value type implements its interfaces via **extension methods** (never structurally), and the converting assembly cannot `partial` a foreign type — the same reason the implicit both-foreign value arm exists (`syscall.Signal`→`os.Signal`). When the defining assembly already implements the pair (its package_info carries the value-form record), `convertToInterfaceType` falls through and the emission stays the plain cast spelling. LOCAL value sources deliberately keep the plain-cast/partial-impl route (no churn, no redundant records). The outer cast is load-bearing exactly as in the pointer arm — and additionally because `var signOpts = …` must type as the INTERFACE: each tls site reassigns `signOpts` to a different adapter two lines later (CS0029 hazard if the var typed as the adapter class). (Guarded by the `CrossPkgLib`/`CrossPkgUser` extension — `Verdict` implements `Scored` via a value receiver with deliberately NO witness in the lib, `CrossPkgLib.Scored(CrossPkgLib.Verdict(4))` converts explicitly in the user package and the same var is then reassigned a local `*tallies` implementation, output-compared vs Go; whole-stdlib reconvert diff: exactly the four crypto/tls sites plus its package_info record.)

**No exported adapter — the LOCAL adapter for a foreign pair.** When the defining package never
converts the pair itself (os never casts `*File` to `io.Reader`, so no record exists to
reference), the converting package records `GoImplement<os_package.File, io_package.Reader>(Pointer
= true)` **locally** and the generator emits a **local adapter class** for the foreign struct
(`internal sealed class os_FileжReader`; the `m_box` field is fully qualified). The class name is
**package-qualified** (`{pkg}_{Struct}ж{Iface}`): two same-named foreign structs adapting to one
interface otherwise compose a single colliding class — math/big records both `bytes.Reader` and
`strings.Reader` against `io.ByteScanner` (CS0102/CS0111/CS8646 ×8). The local VALUE adapters for
foreign structs qualify the same way (`syscall_ΔSignalᴠΔSignal`); a LOCAL delegate's value adapter
stays bare (`funcValueᴠValue`). Forwarding decisions come from **metadata** — the compiled foreign
assembly exposes every converter and sibling-generator form as real symbols, so an extension on
`ж<T>` binds the box (`m_box.Read(p)`) and everything else binds the deref'd value
(`m_box.Value.M()`, ref extensions bind through the ref-returning `Value`). This replaces the old
deref-COPY fallback, so aliasing is faithful: fmt's `Fscan(os.Stdin, …)` emits
`Fscan(new os_FileжReader(os.Stdin), …)` (CS1503 ×3, the last fmt family). Guarded by
`CrossPkgUser` (`*Probe → Sampler` via `CrossPkgLib_ProbeжSampler`, mutation read back through the
original pointer).

### A cross-package interface's unexported sealing marker is stubbed
Go seals an interface to its defining package with an **unexported marker method** — `ast.Expr`'s
`exprNode()`, `ast.Stmt`'s `stmtNode()`, `ast.Decl`'s `declNode()`, `text/template/parse.Node`'s
`tree()`/`writeTo()`. The method's C# implementation is an **internal** extension in the interface's
own assembly (`internal static void exprNode(this ref IndexExpr _)`), so an adapter generated where
the interface is CONSUMED — `go/internal/typeparams` casting go/ast's `*IndexExpr` to `ast.Expr`, or
`text/template` casting `*parse.RangeNode` to `parse.Node` — cannot see it: forwarding
`m_box.Value.exprNode()` is CS1061. The C# interface member itself is public (unexported Go methods
render without a modifier), so it is still *required* — dropping it is CS0535, and an *internal*
interface member cannot be implemented cross-assembly at all. Because Go never lets a sealing marker
be called from outside its package, the adapter satisfies the member with a **no-op / `default!`
stub** instead of forwarding:
```csharp
void global::go.go.ast_package.Expr.exprNode() { }                       // void marker
global::go.…parse_package.Tree global::go.…parse_package.Node.tree() => default!;   // non-void marker
```
The `ImplementGenerator` flags a method as an inaccessible marker when its Go name is unexported
(`GetScope == "internal"`), its declaring assembly differs from the one the adapter is generated
into, **and the struct declares no method of that name in the current compilation**
(`MethodInfo.IsInaccessibleMarker`); a SAME-assembly impl keeps forwarding (the internal extension is
accessible there). Both the pointer (`AdapterImplTemplate`) and value (`ValueAdapterImplTemplate`)
adapters emit the stub. This greens `go/internal/typeparams` (whose only errors were the two
`exprNode` forwards) and is a prerequisite for `text/template`/`go/doc`. (Guarded by
`CrossPkgLib`/`CrossPkgUser`: the sealed `Emitter` interface with an unexported `emitNode()`, a
`*Leaf` implementing it, cast to `Emitter` in the consumer assembly — CS1061 without the stub.)

That third clause is the correction the white-box test model forced (2026-08-09, `internal/profile`).
The assembly comparison is a *proxy* for "there is nothing to forward to", and it answers wrongly for
the one shape where a single Go package spans two C# assemblies: an **INTERNAL (white-box) test
package**. `internal/profile`'s `proto_test.go` is `package profile` — it declares `packedInts` and
its `encode`/`decoder` methods for the production package's own unexported `message` interface. Same
Go package, different C# assembly, and genuinely reachable, because the test model mints an
`InternalsVisibleTo` grant for exactly this. Stubbing there is worse than a compile error: the
adapter COMPILES and *silently does nothing*, so `marshal(source)` returned an empty buffer and
`unmarshal` decoded nothing, with no diagnostic at any layer. Requiring the absence of a local
implementation — the struct's own value/ref extensions plus its direct-`ж` primaries — leaves every
genuine marker stubbed unchanged, because a FOREIGN struct never declares the sealing method (Go
forbids implementing another package's unexported method at all, so a `[GoImplement]` record naming
an unexported interface method can only come from a struct in that same Go package). The guard is
`internal/profile`'s own banked suite: the shape needs a white-box test package, which the behavioral
corpus has no way to express.

### A dynamic interface's runtime conversion class re-escapes a keyword method name
An anonymous or type-asserted interface is lifted to a `[GoType("dyn")]` partial interface (see
[Anonymous interfaces used as an adapter target](#anonymous-interfaces-used-as-an-adapter-target-are-lifted-package-wide)),
and for the dynamic form `go2cs-gen`'s `InterfaceTypeTemplate` additionally emits a **runtime
conversion class** — `ΔI<ᴛTTarget> : I` — that duck-types a target at run time by reflection-binding
each interface method to the target's extension methods (the fallback for a duck-typed assertion the
compile-time `ImplementGenerator` could not resolve). When such a dynamic interface **embeds** an
interface carrying an unexported *sealing* method whose name is a C# reserved keyword — internal/testenv's
`interface{ testing.TB; Deadline() (time.Time, bool) }`, where `testing.TB` has `private()` — that name
must be `@`-escaped in the generated class. The converter already escapes it in the interface itself
(`void @private();`), but the sealing method reaches the conversion class through the base-interface walk
(`interfaceSymbol.AllInterfaces`), and a symbol name read from Roslyn (`IMethodSymbol.Name`) arrives
**UNescaped** — unlike a syntax `Identifier.Text`. Emitting it raw yields `void private()` and
`nameof(private)`; that syntax error corrupts the class body, and because the conversion class is nested
inside the `public static partial class …_package` container the parse recovery ejects every subsequent
operator into the *static* container — **CS0715** ("static classes cannot contain user-defined operators")
×25 plus a **CS0246** cascade (~54): 84 errors from one keyword method. (The nesting itself is legal — a
non-static class nested in a static class holds instance members and operators fine, as every non-keyword
dynamic interface proves; only the broken body triggers the eject.)

The fix re-escapes the name only where it is emitted as its own identifier token — the method declaration
(`MethodInfo.GetSignature`) and each `nameof(...)` in the reflection-binding static constructor. The
compound delegate/field names (`{Name}ByPtr`, `s_{Name}ByPtr`) stay on the raw name: a keyword + suffix
is never itself a keyword, and `@` cannot appear mid-token. `EscapeCsKeyword` is a no-op for every
non-keyword method, so all other dynamic-interface output is byte-identical. Emitted form:
```csharp
internal class ΔcommandContext_type<ΔTTarget> : commandContext_type
{
    private delegate void privateByPtr(ж<ΔTTarget> targetʗ);              // compound name — raw
    public void @private() { … }                                         // declaration — escaped
    // static constructor:
    extensionMethod = targetType.GetExtensionMethod(nameof(@private));   // nameof — escaped
}
```
Greens internal/testenv (its only errors were this one method's cascade). Guarded by the
`DynamicInterfaceKeywordMethod` behavioral test — a named `TB` interface with a `private()` sealing
method, embedded in a type-assertion's anonymous interface so the lifted `[GoType("dyn")]` target's
conversion class must implement the escaped `@private()`; it does not compile without the fix.

### A keyword-named type's interface adapters escape declarations and compose class names unescaped
A Go **type** whose name is a C# reserved keyword (`type fixed struct{…}`, `type lock interface{…}`) is
`@`-escaped by the converter everywhere it stands as its own identifier token (`[GoType] partial struct
@fixed`, `ж<@fixed>`, `@lock l = f`). Two other name paths mishandled such types:

1. **`ImplementGenerator`'s emitted type positions.** A LOCAL struct's name reaches the generator as a
   bare Roslyn SYMBOL name — UNescaped, unlike display strings (`ToDisplayString()` uses
   `CSharpErrorMessageFormat`, which escapes, so `go.main_package.@lock` arrives correct). Emitting the
   raw name produced `partial struct fixed : sizer` — which the C# parser reads as a *fixed-size-buffer*
   declaration, ejecting mangled members into the static `…_package` container (**CS0708**
   `'main_package.'` "cannot declare instance members in a static class" plus a CS1642/CS1663/CS7092
   buffer cascade) — and the same raw name inside the pointer adapter's `ж<fixed>`. The generator now
   applies `EscapeCsKeyword` at those emission sites (`InterfaceImplTemplate.StructName`, the pointer
   adapter's wrapped `StructName`, and the value-embed hop's class qualifier); it is a no-op for every
   non-keyword name.

2. **Adapter class-name composition, BOTH sides.** `@` is only legal at the START of a C# identifier
   token, so a keyword part cannot carry its marker into a composed adapter name: the converter emitted
   `new @fixedж@lock(Ꮡf)`, which lexes as TWO tokens (`@fixedж` + `@lock` — CS1526). Both composers now
   build from UNESCAPED simple names — the converter's `adapterTypeRef`/`valueAdapterTypeRef` via
   `stripSanitizationMarkers` (which also clears a pre-qualified `os_@fixed`-style interior marker), and
   the generator's `AdapterName` compositions via `GetUnsanitizedIdentifier` — producing
   `fixedжlock`/`fixedᴠlock`. The composed name always contains the `ж`/`ᴠ` infix or a package prefix,
   so it is never itself a keyword and needs no marker (the same rule the keyword-method compound names
   above rely on: a keyword + suffix is never a keyword).

Emitted form (from the `KeywordNamedTypes` goldens and its generated adapters):
```csharp
sizer p = new fixedжsizer(Ꮡf);                          // converter cast site — composed, no marker
@lock lp = new fixedжlock(Ꮡf);

partial struct @fixed : global::go.main_package.@lock   // generator value-form — escaped declaration

internal sealed class fixedжlock : global::go.main_package.@lock, IжAdapter
{
    private readonly ж<@fixed> m_box;                   // escaped type reference
```
`TypeGenerator` and `RecvGenerator` were already correct — they read syntax `Identifier.Text`, which
keeps the `@fixed` spelling. Guarded by the `KeywordNamedTypes` behavioral test: struct `fixed` value-
and pointer-implementing `sizer` plus a keyword-named interface `lock`, with a pointer-receiver `grow`
exercising the RecvGenerator ж-twin on the keyword-named receiver.

### An interface member's keyword-named PARAMETERS escape in every generated implementation

The same symbol-vs-syntax asymmetry reaches parameter NAMES. `sync.Map`'s

```go
func (m *Map) CompareAndSwap(key, old, new any) (swapped bool)
```

is emitted by the converter with the keyword escaped (`any @new`), but `ImplementGenerator` re-reads
the members off the interface SYMBOL when it realizes a `[GoImplement]` record, and
`IParameterSymbol.Name` — like `IMethodSymbol.Name` above — arrives with the escape stripped. Every
template renders that name straight into a declaration and a forwarding call, so the generated
explicit implementation emitted `object new`, which does not lex as a parameter; Roslyn's recovery
reported **CS0501** ("must declare a body because it is not marked abstract, extern, or partial") on
the enclosing member — three of them in sync's converted test build, one per type implementing the
test's `mapInterface`.

The three symbol→`MethodInfo` projections (the interface-adapter path, the struct-adapter/explicit-impl
path, and `MethodInfo`'s own `IMethodSymbol` overload) duplicated the same tuple construction, so the
escape lands once in a shared `ToParameterInfos` extension that all three now call; it also carries the
`in`/`ref`/`out` prefix the two adapter paths need (an explicit implementation must reproduce the
ref-kind or it matches no member, CS0539). `EscapeCsKeyword` is a no-op for every non-keyword and for
an already-escaped name, so all other generated output is byte-identical:

```csharp
bool global::go.sync_test_package.mapInterface.CompareAndSwap(object key, object old, object @new)
    => m_box.CompareAndSwap(key, old, @new);
```

Guarded by the `SymbolParameterInfoTests` GenTests cases (name escaping, ref-kind composition, and a
parse assertion on the rendered declaration + forwarding call — the failure is a *parse* failure, so
the string compare alone would not pin it) and by the `InterfaceKeywordParamNames` behavioral test,
which drives BOTH realization shapes — a pointer-receiver implementation (the `ж<T>` adapter) and a
value-receiver one — through an interface whose members declare `new`, `lock`, `base` and `event`
parameters, values vs Go.

### A keyword-named addressed global's heap-box field strips the escape after the Ꮡ prefix
An address-taken package-level var is backed by a heap-box FIELD plus a ref-returning property
(`writeAddressedGlobalDecl`). A keyword-named such global (`var null = json.RawMessage([]byte("null"))`,
net/rpc/jsonrpc) arrives keyword-escaped (`@null`), and composing the box as `Ꮡ` + `@null` places the
escape INTERIOR to the identifier token — `Ꮡ@null` lexes as two tokens (a whole-file syntax cascade).
The `Ꮡ` prefix already de-keywords the composed name (the keyword + affix rule the adapter compositions
above rely on), so the field declaration strips the escape — matching every `&null` use site, which
already composed `Ꮡnull` through `boxBaseName`:
```csharp
internal static ж<slice<byte>> Ꮡnull = new(slice<byte>((@string)"null"));
internal static ref slice<byte> @null => ref Ꮡnull.ValueSlot;   // the var itself keeps its escape

var p = Ꮡnull;                                                  // use site, unchanged
```
Guarded by `HeapKeywordVar` (a package-level `var null` written through its pointer and read back both
ways), alongside its existing keyword-named LOCAL coverage.

### A foreign struct's promoted method forwards through its value embed
When the adapter's struct is FOREIGN (defined in another assembly) it binds forwarding from METADATA
(the boxBound / refBound scan above); a member neither on its box nor a ref-static falls to
`m_box.Value.M()`. But an interface member the foreign struct PROMOTES through a VALUE-embedded field
has no extension on the struct's OWN package class, so `m_box.Value.M()` is CS1929 — `text/template`
casting `*parse.RangeNode` to `parse.Node`, where `RangeNode` embeds `BranchNode` and the exported
`String` lives on `BranchNode`, not `RangeNode`. The generator now discovers the foreign struct's
value embeds from metadata (`GetForeignValueEmbeds`: a member whose name equals its type's simple
name) and, for a still-unbound member the embed's package class declares as a public value/ref-receiver
extension (`GetForeignValueReceiverMethods`), forwards through the embed's package-class STATIC:
```csharp
global::go.@string global::go.…parse_package.Stringer.String() =>
    global::go.…parse_package.String(ref m_box.Value.BranchNode);
```
The static form is required because the embed's namespace is not imported in the adapter file (only
`using go;`), so an instance-form `m_box.Value.BranchNode.String()` cannot resolve the extension —
exactly as the foreign struct's own extensions route through `staticClass`. The receiver argument
carries the extension's ref-kind (`ref`/`in`/value). Rerouting is gated to a genuinely promoted member
(the struct binds it neither directly nor via a box/ref hop), so a struct that declares the method
itself is unaffected. This clears `text/template`'s last CS1929. (Guarded by `CrossPkgLib`/`CrossPkgUser`:
`*Branch`, which promotes the exported `Emit` through its `EmitBase` value embed, cast to `Emitter` —
CS1929 without the reroute.)

### A promoted box-receiver method through an UNEXPORTED value embed is called cross-package via a public forwarder
An EXPORTED, pointer-receiver method that takes the address of a receiver field is emitted as a
**direct-ж (box-receiver) primary** `M(this ж<T> …)`. When such a method is *promoted* through an
**unexported** VALUE embed — `testing.T.Errorf`, promoted from the embedded `common` (`type T struct{
common; … }`), or go/types' `TypeName`/`Var`/`Func`, which embed `object` — an IN-PACKAGE caller
renders the descent through the embed's box-field accessor (see *Promoted pointer methods descend
multi-hop value-embed chains* above):
```csharp
Ꮡt.of(testing.T.Ꮡcommon).Errorf("…"u8, …);   // in-package
```
`Ꮡcommon` is the `TypeGenerator`'s `FieldReferences` box accessor for the embed, and — matching the
embed's unexportedness — it is `internal`. So a caller in **another package/assembly** (crypto/internal/
cryptotest, testing/slogtest, x/net/nettest, go/internal/gcimporter) cannot see it: a cross-assembly
reference to an `internal` member reads as **CS0117** ("`testing_package.T` does not contain a definition
for `Ꮡcommon`"), not CS0122. Every path through the unexported embed (`common`, `Ꮡcommon`, its promoted
members) is `internal`, so no converter-only descent can reach it. The fix is two-sided:

- **`go2cs-gen` (`StructTypeTemplate`)** — for a direct, non-generic **VALUE embed**, harvest
  the embed's box-receiver primaries (`GetBoxReceiverExtensionMethods`, previously collected only for
  POINTER embeds) and, for each **exported** one, emit a single box-only shim (`IsValueEmbedBoxRecv`) that
  performs the descent internally, where the `internal` accessor is reachable:
  ```csharp
  public static void Errorf(this ж<T> Ꮡtarget, @string format, params Span<object> argsʗp)
      => Ꮡtarget.of(T.Ꮡcommon).Errorf(format, argsʗp);
  ```
  No `this ref T` overload (a box receiver cannot bind on a value). The shim scope is the shared
  `methodScope` — the STRUCT's exportedness, downgraded for a non-public return type — so it is `public`
  only for an exported method on an EXPORTED struct returning void/public (the genuinely reachable case),
  and `internal` on an UNEXPORTED enclosing struct (context's `afterFuncCtx`, reflect's
  `structTypeUncommon`), whose `ж<T>` receiver is itself internal — a `public` shim there is CS0051. It is
  gated to an exported promoted method (an unexported one is never reachable across packages, so it needs
  no shim; its in-package callers keep the inline descent). The value embed is discriminated by
  `!promotedStructType.Contains("<")` (a plain value embed's type name never carries `<`, whereas the
  pointer-box form `ж<…>` and generic embeds do) — a more robust test than the `@`-keyword-escaped
  `pointerEmbedTypeNames` membership, whose `ж<@file>`-shaped names mismatch and mis-fired the shim onto
  os.File's `*file` POINTER embed (a stray `File.Ꮡfile.Value`, CS0119). The embed's own **exportedness is
  NOT part of that discrimination** — it was, until r56g, and the restriction was never a Go rule; see
  *A value embed promotes its pointer-receiver methods into the outer POINTER method set* below for why
  the narrower gate silently truncated a Go method set rather than merely skipping an unreachable shim.
- **the converter (`convSelectorExpr`)** — when the promoted-method descent is reached through an
  unexported embed of a FOREIGN package (single hop), it drops the inaccessible `.of(…)` view and calls
  the promoted method DIRECTLY on the receiver box, binding the public shim:
  ```csharp
  Ꮡt.Errorf("…"u8, …);   // cross-package (Ꮡt for a deref'd param, tΔ1 for a lambda box param)
  ```
  The box is recovered from the first-hop `&embed`-address the `&`-machinery already computes (the text
  before its last `.of(`), so it is correct for every receiver kind without re-deriving it.

(Guarded by `PromotedEmbedLib`/`PromotedEmbedUser`: `Counter` value-embeds an unexported `common` whose
exported `Add`/`Report` take `&c.sum` (box-receiver); the user package calls them on a `*Counter` local
and through a parameter, plus reads the exported `Label` field for contrast — output-compared vs Go.)

**Plain-return-type addendum — a PLAIN (non-box) promoted method returning a public builtin.** The
box-shim above covers a method emitted as a `ж<T>` primary (it takes `&receiver.field`). A method that
merely READS a field — `testing.common.Name()` (`func (c *common) Name() string { return c.name }`) — is
emitted as an ordinary `Name(this ref common)` extension, so the promotion machinery emits the usual
value + box forwarders `Name(this ref T)` / `Name(this ж<T>)` with body `target.common.Name()`. But their
scope was downgraded by the RETURN type: `@string` (and `error`, `bool`, `nint`, … — every golib builtin)
is a PUBLIC C# type whose Go-lowercase name the name-based `GetScope` heuristic reads as unexported, so
the forwarder was emitted `internal` and thus invisible cross-assembly. Cross-package the converter emits
the same bare `Ꮡt.Name()` (the foreign-unexported-value-embed arm fires for EVERY promoted
pointer-receiver method, not just box ones), which then bound a same-named FOREIGN extension —
`x/net/nettest`'s `timeoutWrapper` reads `t.Name() == "…"`, and the only visible `Name` was
`flag.Name(ref flag.FlagSet)` (flag is imported by testing) → **CS1929**. The fix keeps the forwarder
public when its return type is GENUINELY accessible: `go2cs-gen` captures the return type's ACTUAL C#
accessibility (`MethodInfo.ReturnTypeIsPublic`, computed by `IsEffectivelyPublicType` — the type and
every type argument / tuple element / array-or-pointer element is `public`, treating builtin special
types and use-site-bound type parameters as public) and, for the direct-unexported-value-embed case,
trusts it over the lowercase name (`directEmbedIsUnexportedValue && method.ReturnTypeIsPublic`):
```csharp
public static @string Name(this ж<T> Ꮡtarget) { ref var target = ref Ꮡtarget.Value; return target.common.Name(); }
```
Every OTHER promotion keeps the conservative name heuristic (so no golden/compile churn), and an
UNEXPORTED enclosing struct still yields an internal forwarder (its `ж<T>` receiver is internal — a public
forwarder there is CS0051, and my change only prevents a downgrade below the struct's own scope). This
greens `x/net/nettest` (census 271 → 272/302, zero regressions). (Guarded by `PromotedValueEmbedLib`/
`PromotedValueEmbedUser`: `Widget` value-embeds an unexported `common` whose plain `Name() string` is read
in an expression cross-package, alongside an unrelated `Gadget.Name()` — the foreign same-named extension
— output-compared vs Go; CS1929 without the fix.)

**Pointer-expression-receiver addendum.** The converter arm above recovers the box from the `.of(…)`
strip of the first-hop `&embed` address — which assumes the receiver has an addressable base (an
ident: a raw-box local, a deref'd param's `Ꮡx`). A pointer receiver **expression** — a type-assert or
call chain like go/internal/gcimporter's `pkg.Scope().Lookup(name).(*types.TypeName).Type()` — has no
such base: the `&`-machinery boxes a COPY (`Ꮡ(x.@object)`, no `.of(` anywhere), so the arm silently
fell through to the spelled embed hop, `internal` cross-assembly (**CS1061**). A follow-up sub-arm
recognizes a pointer-typed receiver expression that renders as the raw box (pointer-typed and not
deref-aliased) and calls the promoted member straight on it — the box IS the receiver:
```csharp
pkg.Scope().Lookup(name)._<ж<types.TypeName>>().Type();   // binds the public Type(this ж<TypeName>)
```
(Guarded by `PromotedValueEmbedExprRecv`: the promoted `Name()` called on a `map[string]any`
assert-chain receiver and on a constructor-call receiver, output-compared vs Go; CS1061 without the
fix.)

### A value embed promotes its pointer-receiver methods into the outer POINTER method set
Go's rule has no exportedness clause: for `type S struct{ E; … }` embedding `E` **by value**, the method
set of `*S` contains every **pointer**-receiver method of `E`, because `&s.E` is addressable. (The method
set of a plain `S` does **not** — that half is the narrowing this subsection also has to preserve.)

The shim above emitted exactly that promotion, but only for an **unexported** embed. That gate arrived
with the cross-package-reachability problem it solves (`testing.T.Errorf`, whose `Ꮡcommon` accessor is
`internal`) and reads as a scoping decision, which is why it looked harmless: for an EXPORTED embed the
accessor is public, so the converter's own call sites descend inline and never need a shim.

They are not the only reader. **golib reconstructs a Go method set at RUN TIME by scanning the EMITTED
extension methods** (`TypeExtensions.GetGoMethodSetCandidates`, shared by the `StructurallyImplements`
probe and `AdapterBinder`'s shell binder — see *Every eligible interface carries runtime duck-typing
shells*). So an un-emitted promotion is not a missing convenience, it is an **ABSENT Go method**: the type
stops satisfying interfaces Go says it satisfies, at every site the compile-time recorders cannot reach.

`debug/dwarf` is the reached case. Its `readType` asserts to an **anonymous** interface —

```go
typ.(interface{ Basic() *BasicType }).Basic()
```

— which the converter lifts to a package-local `[GoType("dyn")] partial interface readType_type`. The
concrete types (`*IntType`, `*UintType`, `*CharType`, `*UcharType`, `*FloatType`, …) satisfy it **only**
through `func (b *BasicType) Basic() *BasicType` promoted from their exported `BasicType` value embed, and
the value is held as a *different* named interface (`Type`) at the assertion site — so no compile-time
witness can exist for the pair and the run-time tier is the only thing that can answer. It answered MISS:

```
panic: interface conversion: interface {} is *dwarf.UintType, not dwarf.readType_type
```

The gate is now the Go rule — any direct, non-generic VALUE embed promotes its box-receiver primaries —
and the shim keeps its `ж<S>`-only receiver, which is what preserves the narrowing half (a `Uint` VALUE
must still miss the same anonymous interface):

```csharp
public static ж<BasicType> Basic(this ж<UintType> Ꮡtarget) => Ꮡtarget.of(UintType.ᏑBasicType).Basic();
```

The `of(…)` view is load-bearing rather than incidental: it **aliases** the embedded storage, so dwarf's
caller writing `t.Name`/`t.BitSize` through the returned `*BasicType` reaches the real field. A
copy-returning promotion would have compiled, run, and printed plausible zeros.

**Reachability addendum — the shim was emitted, and emitted unreachable.** Widening the collection gate
made the promotion EXIST; it did not by itself make it bindable. The shim's scope is the shared
`methodScope`, whose return-type downgrade runs the name heuristic — and `GetSimpleName` reduces a type to
its last dotted segment, which for a Go MULTI-RETURN is `error)`. Lowercase. So *every tuple-returning*
promoted method read as unexported and was emitted `internal`. `archive/zip` is the reached case: `Open`,
promoted from `ReadCloser`'s exported `Reader` embed, returns `(io.fs.File, error)`, so the package's own
test assembly could not bind the shim its `ReadCloser`→`fs.FS` adapter needed (**CS1929**, the whole
package build-blocked behind it). The accurate test (`method.ReturnTypeIsPublic`, from
`IsEffectivelyPublicType`, which walks tuple elements) already existed for the plain-return case; it now
also applies to `IsValueEmbedBoxRecv`, which is the *stronger* case for it — that shim exists precisely to
be reachable across assemblies, since it performs a descent the caller cannot spell, so emitting it
`internal` defeats its own purpose. Every other promotion keeps the conservative heuristic.

Only the **collection** gate widened. The return-type relaxation's OTHER arm
(`directEmbedIsUnexportedValue && method.ReturnTypeIsPublic`, in the plain-return addendum above) stays on
the narrow condition, because it answers a different question — a cross-package call the converter emits
as a bare `Ꮡt.M()` — which remains the unexported-embed case alone.

### A named field whose name equals its interface type is NOT an embedded interface
`ImplementGenerator` forwards an interface member it cannot bind directly through an **embedded interface
field** (zip's `type nopCloser struct{ io.Writer }` → `m_box.Value.Writer.Write(…)`), and detects one by
NAME: the field's name equals its interface type's simple name, modulo the `Δ` collision marker (the
converter names the field after the Go embed, so a Δ-renamed interface TYPE keeps a markerless FIELD —
slogtest's `wrapper` embeds `slog.Handler` as `Handler`).

That test cannot, by itself, be right. Go emits an **ordinary named field** `Type Type` and an **embedded**
`Type` to the same C# field declaration, and only the first promotes nothing. `debug/dwarf` carries both
shapes in ONE struct:

```go
type PtrType struct {
	CommonType        // a real embed — promotes Common()
	Type       Type   // an ordinary field — promotes nothing
}
```

`Common()` was therefore forwarded through the FIELD, returning the **referenced** type's `CommonType`
instead of the receiver's own — a silent wrong answer whenever `Type` was non-nil, and a null dereference
when it was not. Five dwarf structs carry that field (`QualType`, `ArrayType`, `PtrType`, `StructField`,
`TypedefType`).

Resolved by **precedence**, not by a new signal — none is available, since the two emissions are identical
by construction. Promotion through a marker-backed **depth-1** value embed (`public partial ref CommonType
CommonType { get; }` — a hard converter marker the name heuristic is not) now resolves BEFORE the
interface-field arm:

```csharp
ж<CommonType> ΔType.Common() => m_box.of(PtrType.ᏑCommonType).Common();   // not m_box.Value.Type.Common()
```

Legal Go guarantees the two can never both be correct at depth 1: promoting one member from two depth-1
embeds is an **ambiguity the Go compiler rejects**, so a struct where both arms answer is a struct whose
"interface embed" is really a plain field. Deeper embed levels stay BELOW the interface arm, matching Go's
shallower-embed-wins rule. Implemented by running the existing `.of(…)` descent in two passes (`maxDepth`
1, then 4) so the "what can bind at this hop" logic is not duplicated and cannot drift from itself.

(Both subsections guarded by `PromotedEmbedAnonIfaceWitness`, which pins the anonymous-interface assert
from a named-interface-held value, the two-concrete-type spread, the type-switch form, the ALIASING write
back through the promoted pointer, the VALUE-must-miss narrowing, and the `Ptr{CommonType; Node Node}`
shape checking the VALUE `Common()` returns rather than merely that nothing crashed. `debug/dwarf`
30 → 40 of 40.)

### Cross-package value-to-interface conversions use the local VALUE adapter
A VALUE conversion of a FOREIGN named type to a LOCAL interface (os's `Signal` interface is
DOWNSTREAM of `syscall.Signal` — neither assembly can partial the other) records
`GoImplement<foreign, localIface>` locally; the `ImplementGenerator` detects the foreign struct
(different containing assembly, no local declaration) and emits a **value adapter class**
`{pkg}_{Struct}ᴠ{Iface}` (composed with `Symbols.ValueAdapterInfix`; package-qualified for a
FOREIGN struct — see the pointer-adapter collision note above) wrapping a **COPY** of the struct
— exactly as a Go interface holds a value — with value equality. The conversion site emits
`new syscall_ΔSignalᴠΔSignal(sig)`. The adapter's struct field is **fully qualified**
(`GetFullTypeName(true)`): the bare name resolved to the LOCAL same-named type when os's
`ΔSignal` interface shadowed syscall's `ΔSignal` struct.

Method forwarding uses the **container-qualified static form** —
`global::go.encoding.binary_package.Uint32(m_value, b)` rather than `m_value.Uint32(b)`:
converted Go methods are extension methods on the package class the struct nests in, and the
instance form only resolves when the generated file has a `using` for that namespace (`using go;`
covers root-namespace packages like io/os, but a sub-namespace package like `encoding/binary`
never resolved — debug/plan9obj CS1061 ×6). The static form is exactly equivalent and needs no
using at all.

**BOTH-FOREIGN value pairs take the same route.** When the interface is foreign too
(debug/plan9obj passes `binary.BigEndian`, an `encoding/binary` value, as `binary.ByteOrder`),
the converter first consults the imported package_info records (`parseExportedValueImplements`,
plain or `Promoted` `GoImplement` forms): if the defining assembly already implements the pair,
the bare value converts implicitly and nothing is recorded. Otherwise the pair is recorded
locally and the conversion site wraps in the locally generated value adapter
(`new binary_bigEndianᴠByteOrder(binary.BigEndian)`) — the value sibling of the both-foreign pointer
adapter above.

**A value adapter declares `IValueAdapter`, so the runtime sees the Go DYNAMIC TYPE through it.**
The adapter is a C# wrapper class, but Go's dynamic type of the interface value it carries is the
wrapped struct. golib settles that question at three places — `AreEqual` (Go `==`), `TryTypeAssert`
(`x.(T)` and every type-switch case guard), and `type()` (the type-switch operand) — and each one
unwraps the OTHER two adapter kinds through their marker (`IжAdapter.Box`, `IInterfaceAdapter.Value`)
while the value adapter carried no marker at all. So all three answered against the **wrapper class**:

```go
got := dst.At(0, 10)                 // image: color.Color carrying a color.NRGBA
got == color.NRGBA{…}                // Go true  -> C# false (AreEqual: type mismatch, bails
                                     //            before the adapter's own Equals ever runs)
v, ok := got.(color.NRGBA)           // Go ok=true -> C# ok=false
switch got.(type) { case color.NRGBA: … }   // Go matches -> C# fell to default
```

The panic text made the shape unmistakable: `interface conversion: interface {} is
colorlike.NRGBA, not colorlike.NRGBA` — `%T` already unwrapped (through `TryAdapterWrappedType`)
while the assert did not. The fix is the missing third marker, mirroring the other two exactly:

```csharp
public interface IValueAdapter : IGoAdapter { object? Value { get; } }
```

`ImplementGenerator`'s `ValueAdapterImplTemplate` now emits it on every `ᴠ` adapter —
**explicitly** implemented, so it can never collide with a forwarded Go method named `Value` (a
promoted adapter binds its members by bare name) — and the three sites unwrap it beside the pointer
kind through one shared `UnwrapAdapter` helper, still behind the single `IGoAdapter` probe that
keeps an ordinary Go value at one failing interface test. `GetGoTypeName`, `GoDynamicTypeOf` and the
reflect-bridge assignability check gained the same arm, and `GoReflect.TryAdapterWrappedType`
switched from a NAME probe for the `ᴠ` infix plus a package-class nesting check to the exact marker
test. One marker fixes equality, both assert forms, the type switch and `%T` together — an
`Equals`-only fallback would have fixed equality alone and left the dynamic-type rule weaker.

Unwrapping in the assert's **interface** tier matters independently: the adapter class carries only
the ONE interface it was generated for, so probing it can never resolve the wrapped struct's other
interfaces — `image/gif`'s `m.ColorModel().(color.Palette)` and `c.(color.RGBA)` are exactly that
shape.

Only the CROSS-ASSEMBLY shape reproduces any of this: a same-package value conversion implements the
interface on the struct directly (the value-boxing partial-struct implementation), so there is no
wrapper and never was a problem — which is why the defect stayed invisible until packages like
`image`/`image/draw` converted a foreign struct to a foreign interface. (Guarded by the
`ValueAdapterDynamicType` behavioral test: a sibling `colorlike` package declares the interface and
two value-receiver implementers, `main` does the conversion, and the test exercises `==` both
operand orders, interface-vs-interface equality, both type-assert forms plus a miss, a type switch
over both implementers, `%T`, method dispatch and an interface-keyed map — output-compared vs
`go run`.)

### Under `-tests`, a white-box PRODUCTION type is FOREIGN to the generator — so the name carries the prefix
The two sides of an adapter name must compose it identically, and they answer *"is the source type
foreign?"* by different means: the generator tests the **containing assembly**, the converter tests the
**Go package**. Under the white-box reference model those two disagree about exactly one set of types —
the package under test's own. `go/packages` merges the production files into the INTERNAL test variant's
Go package, so `pkg == v.pkg` reads `net.Conn` as local; its C# lives in the *referenced* production
assembly, which the generator reads as foreign and therefore prefixes.

The value arm already carried that carve-out (`whiteboxProductionTarget`, added for `encoding/binary`).
The **interface**-sourced arm did not, so every cast site named a type that does not exist — 26 CS0426
across seven of net's internal test files, 55% of everything left after the r27 syntax cascade closed:

```csharp
new net_test_package.ConnᴠReader(c)        // referenced (arm composed the name unprefixed)
public sealed class net_ConnᴠReader : …     // generated (prefixed, in the SAME anchor class)
```

The anchor was never in doubt — the record lands in `package_test_info.cs` and the class is generated
into the test metadata class both sides name. Only the *simple name* disagreed. `isSameAssemblyPkg`
is deliberately left alone: it already answers correctly (both reference models clear
`testPackagePath`), and it remains the RECOMPILE fallback's answer, where production sources really do
compile into the test assembly. Behavioral CNR is byte-identical — the shape exists only under
`-tests`.

### An exported func type publicizes the unexported types in its signature
An EXPORTED named func type becomes a `public` C# delegate; an unexported type in its signature —
x/text/unicode/bidi's `type Option func(*options)`, where `options` is package-private — is then
less accessible than the delegate (CS0059, "inconsistent accessibility"). The type-accessibility
pass, which already publicizes the unexported types exposed by an exported struct field / package
var / method signature, also walks an exported named type whose underlying is a `*types.Signature`
and publicizes the unexported named types in its parameters and results:

```go
type options struct{ … }        // unexported
type Option func(*options)       // exported -> public delegate
```
```csharp
[GoType] public partial struct options { … }   // publicized to match the delegate
public delegate void Option(ж<options> _);
```

Only a package with an exported func type over an unexported type is affected (no golden churn). (Guarded by the `PublicizedFuncTypeParam` behavioral test.)

**A func-TYPED exported field or var publicizes the unexported types in the func signature.** The
accessibility walk that publicizes an unexported type exposed by an exported field / package var
(`collectUnexportedNamedTypes`, CS0052) peels `pointer`/`slice`/`array`/`map`/`chan` wrappers to reach
the element type — but stopped at a `*types.Signature`, so an unexported type reachable ONLY through a
func-typed field's signature was left `internal`. crypto/internal/hpke's

```go
type hkdfKDF struct{ … }                          // unexported
var SupportedKDFs = map[uint16]func() *hkdfKDF{…}  // exported var -> public field
```

emits `public static map<uint16, Func<ж<hkdfKDF>>> SupportedKDFs`, whose type embeds `hkdfKDF` through
the func RESULT — but `[GoType] partial struct hkdfKDF` defaulted to `internal`, less accessible than
the public field (CS0052). `collectUnexportedNamedTypes` now has a `*types.Signature` case that recurses
into the signature's PARAMS and RESULTS through the same named-only walk (which handles a nested func
result in turn), so `hkdfKDF` is publicized to `[GoType] public partial struct hkdfKDF` (and its exported
methods go public via the receiver-access cascade). Both sides of the signature are covered — a func
PARAMETER exposes an unexported type just as a func RESULT does (`var Appliers = []func(*cfg)` →
`public static slice<Action<ж<cfg>>> Appliers`, publicizing `cfg`). This routes through the named-only
`collectUnexportedNamedTypes`, NOT the signature-context `collectSignatureTypes`: a lifted anonymous
struct/interface written in the func signature stays the CS0050/CS0051 signature domain, so only genuinely
func-reachable NAMED types publicize here. (Guarded by the `FuncFieldUnexportedType` behavioral test — a
public `map[uint16]func() *hkdfState` var whose func result exposes an unexported type, plus a
`[]func(*cfg)` var whose func parameter exposes another, output-compared vs Go; both fail CS0052 without
the publicize.)

**A publicized wrapper reaches through an UNNAMED composite RHS to its element type.** A defined type whose `[GoType]` wrapper is emitted `public` (exported, or unexported-but-publicized) exposes its written RHS through the wrapper's `Value`/ctor/indexer/operators, so an unexported RHS type must be publicized too. This holds not just for a NAMED RHS (`type EncoderBuffer encoder`) but for an UNNAMED composite RHS whose ELEMENT is an unexported named type: `type ringElement [256]fieldElement` exposes `fieldElement` through the array-wrapper's indexer/`Value`/`ToSpan`, so `fieldElement` must be publicized (crypto/internal/mlkem768, CS0050/CS0051/CS0053/CS0054/CS0056/CS0057). `collectPublicizedWrapperRHS` therefore feeds the RHS unconditionally to the pointer/slice/array/map/chan-peeling walk (`collectUnexportedNamedTypes`) rather than gating on a named RHS. The walk has no `*types.Struct` case, so a struct RHS stays a no-op — an exported field of an unexported struct-field type is the CS0052 domain and is intentionally left internal. (Guarded by the `NamedArrayWrapper` extension — an exported `Grid [3]unit` over an unexported `unit`, output vs Go.)

### A test-file exported helper over an unexported PRODUCTION type is emitted `internal` (the MIRROR)
The publicization passes above run over a single `*types.Package` and raise a production type's
accessibility to match the exported production surface that exposes it. A `_test.go`-declared helper
is the **mirror** case and needs the *opposite* resolution. In the `-tests` pipeline the production
sources are converted first and independently (no test files), so an unexported production type is
already emitted `internal` on disk; the test files are converted afterward. Go's `strconv/internal_test.go`
declares an EXPORTED helper returning that internal production type:

```go
// internal_test.go (package strconv) — exports access to strconv internals for tests
func NewDecimal(i uint64) *decimal { … }   // decimal is package-private, emitted `internal`
```

Emitting `NewDecimal` `public` (its capitalized name) makes it a public method whose result is the
less-accessible internal `decimal` — CS0050. Publicizing `decimal` is **not** the fix here: production
was already emitted (and is not re-emitted in the test pass), so the map entry would be inert, and a
public API surface for a test-only helper is semantically wrong. In the recompile test model the test
assembly is self-contained (production + internal-`package strconv` + external-`package strconv_test`
files all compile into ONE assembly, with no cross-assembly consumer of a test symbol), so the correct
and sufficient resolution is to downgrade the helper to `internal`:

```csharp
internal static ж<@decimal> NewDecimal(uint64 i) { … }   // was public → CS0050; internal ≤ any prod access
```

`visitFuncDecl` downgrades an exported free function (`Recv == nil`) declared in a `_test.go` file when
its signature references, in any param/result position (peeling pointer/slice/array/map/chan),
an unexported same-package named type **declared in a production file**
(`signatureReferencesUnexportedProductionType`). The production-file restriction is essential and is
what distinguishes this from `sort`'s `example_multi_test.go`, whose exported `OrderedBy(...) *multiSorter`
returns an unexported type declared **in a test file**: that type is publicized AND re-emitted `public`
within the same test pass (the framework above), so its referrer compiles as public and must **not**
be flipped. Only a production-declared unexported type stays `internal`-on-disk and forces the
downgrade. The change fires solely inside `_test.go` conversion, so normal-path output is byte-identical
(check-no-regression clean across the behavioral corpus) and no already-validated package drifts
(`sort` re-validates 63/63, `SetOptimize(bool) bool` in the same file stays `public`). This is the
blocker that lets `strconv`'s test host reach compilation of its file-reading suites (`TestFp`/`TestAtof`
read `testdata/testfp.txt` via `os.Open` + `bufio.Scanner`). No behavioral guard is expressible — the
`-tests` recompile model has no normal-path analogue — so the guard is the `strconv` pipeline (its
`internal_test.cs` emits `NewDecimal` `internal`; the CS0050 no longer blocks).

### A publicized unexported interface is emitted `public`
The accessibility pass records an unexported **interface** used in an exported surface exactly like a
struct or func type — testing's `type testDeps interface { … }` reached through `func MainStart(deps
testDeps, …) *M` is interned into `packagePublicizedTypes`, and `visitTypeSpec` sets
`pendingTypeAccess = "public "`. But on the EMISSION side, every top-level type-kind emitter consumes
`v.pendingTypeAccess` (struct, array, map, ident, the inline selector/star cases) *except*
`visitInterfaceType`, which dropped it — so the interface always emitted `[GoType] partial interface
testDeps`, defaulting to C# `internal`, less accessible than the `public` member that references it
(CS0051). `visitInterfaceType` now reads-and-clears `pendingTypeAccess` at entry (so the lifted/anonymous
interfaces it visits recursively see an empty value) and folds the modifier into the post-attribute slot,
emitting `[GoType] public partial interface testDeps`. Non-publicized interfaces are unchanged (no churn).
(Guarded by the `PublicizedInterfaceParam` behavioral test — an exported function taking an unexported
interface whose method returns a built-in type, output-compared vs Go.) The **transitive** cascade also
walks a publicized interface's method signatures: the `collectMethodSignatureUnexportedTypes` fixpoint step
walked a type's `named.NumMethods()` (declared receiver methods) but that is **0 for a defined interface**
— an interface's methods live on its underlying `*types.Interface`. It now also iterates
`iface.NumMethods()` for a publicized interface, so an unexported NAMED type in a public interface member's
parameter/result signature is publicized in turn (CS0051/CS0050).

**…and an interface member is public whether or not the GO method is exported (2026-08-08).** That
walk still ran each method through a gate that returned early on `!method.Exported()`. The gate is
right for a CONCRETE method — an unexported one emits `internal static … sockaddr(this
ж<SockaddrInet4> …)` and exposes nothing — and wrong for an interface member, which
`visitInterfaceType` emits with **no access modifier** and which C# therefore makes implicitly
**public**. Go's case convention simply does not survive into the emitted surface, so it is the
EMITTED C# accessibility, not the Go exportedness, that decides what must be lifted; the gate now
takes an explicit flag, set only on the interface arm. syscall's `Sockaddr` is the archetype and the
idiom is deliberate Go: `sockaddr() (unsafe.Pointer, _Socklen, error)` is unexported precisely so
that only the package can implement the interface — a SEALED interface — yet the emitted member
returns the unexported `_Socklen` from a public interface (**CS0050** on every unix flavor; Windows
spells the same method with `int32`, which is why the corpus never saw it). `go/types` is the other
reached case, and a subtler one: its exported `Object` interface has `color() color` and
`setColor(color)`, and the wrapper only ever compiled because the type's `Δ` collision-rename made
`TypeGenerator`'s name-based scope rule read the leading Greek capital as exported and emit it
`public` by accident. It is now publicized on purpose. (Guarded by
`typeAccessibilityInterface_test.go`, whose negative controls fail if the gate is dropped outright
rather than narrowed — a concrete unexported method must still publicize nothing.)

A public callable's signature can also reference a **lifted anonymous** type, which the NAMED-only cascade
above cannot reach — testing's `testDeps.CoordinateFuzzing(… corpusEntry …)` / `RunFuzzWorker` / `ReadCorpus`,
where `type corpusEntry = struct{…}` is an ALIAS to an anonymous struct. The signature type is not a
`*types.Named` but a lift (`corpusEntryᴛ1`), a synthesized name over a raw `types.Type` with no
`*types.Object`, so `packagePublicizedTypes` (keyed by object) cannot hold it. A parallel set
`packagePublicizedLiftedTypes` (keyed by the alias-stripped anonymous `types.Type`) fills the gap: a
SIGNATURE-context walker `collectSignatureTypes` — used by the exported-func, exported named-func-type, and
method/interface-method signature paths — records any lifted anonymous struct/interface it reaches, and the
lift emission in `visitStructType` consults `isPublicizedLiftedType` and emits `public`. This is deliberately
**signature-scoped** and does *not* fold into the shared named-only `collectUnexportedNamedTypes`: an exported
**field/var** of an anonymous struct is the CS0052 domain (a public struct/var over an internal anon field
type is legal while its own enclosing type is internal), so only signature positions lift — keeping golden
churn to the one genuinely-affected shape. (Guarded by the `PublicizedInterfaceAnonAlias` behavioral test — an
unexported interface publicized through an exported function, whose method both takes and returns a
`type = struct{…}` alias, output-compared vs Go; it fails to compile with CS0050/CS0051 without the lift
publicize.)

### Publicized unexported types make their exported methods public
An unexported Go type reachable through an exported surface (an exported var — `var BigEndian
bigEndian` — an exported field, or an exported function's signature) is emitted `public`
(`packagePublicizedTypes`, CS0052/CS0050). Its **exported methods** must then be public too —
Go callers hold such values through the exported var and call the methods cross-package, but the
receiver-based access rule alone rendered them `internal` (extension methods invisible outside
the assembly: `binary.BigEndian.Uint32(...)` CS1061). The receiver-access checks in
`visitFuncDecl` treat a publicized receiver as public, and `collectPublicizedTypes` **cascades**
through the publicized types' exported method signatures to a fixpoint (a newly public method's
unexported parameter/result types get publicized in turn, or the public method would be CS0050).
Unexported methods stay `internal` regardless.

### Structural interface satisfaction emits C# interface inheritance
Go converts `fs.File` to `io.Reader` implicitly because the method set suffices; C# interfaces
are nominal. When a declared interface's method set **strictly contains** an EXPORTED method
interface from a **directly imported** package (checked with `types.Implements`), the converter
emits real C# inheritance at the declaration and **skips re-declaring the covered members**
(redeclaring would HIDE the base member — implementers would need both):

```csharp
[GoType] partial interface File :
    io_package.ReadCloser
{
    (FileInfo, error) Stat();
}
```

Every downstream interface-to-interface conversion then becomes an implicit reference
conversion — identity-preserving (the dynamic value flows through type asserts, unlike an
adapter wrapper) and zero-cost (os's `CopyFS` passes an `fs.File` to `io.Copy`, CS1503).
Details: only the **minimal covering set** is listed (`ReadCloser` subsumes `Reader`/`Closer`);
the strict-subset guard rules out inheritance cycles (equal method sets never inherit);
candidates covered by a declared **embed** are skipped (the embed emission handles those);
bases reference the **file-local package alias** (`io.ReadCloser`, user-ruled style) via
getAliasQualifiedTypeName, which also registers the using — needed because the declaring Go file may not
import the candidate's package (`fs.go` declares `File` without importing `io`); lifted/dyn
and constraint interfaces are excluded. **Multiple non-subsuming bases sharing a method**
(`CrossPkgLib.Sealed` and `.Rated` both carry `Label`): both are inherited, and the shared
member is **re-declared** — a member covered by exactly one listed base is inherited/skipped,
but one covered by two or more is re-declared so it hides both inherited slots and member
lookup through the derived interface stays unambiguous (CS0121). Go needs only one method to
satisfy all; the C# implementers satisfy every slot with the same public method. Consequently the converter **never records an interface-to-interface
`GoImplement`** — the generator's impl types are structs, and an interface-typed record kills
its whole run. Bounds (banked): candidates come from direct imports only — same-package
structural pairs, the universe `error`, and non-imported-package pairs would still surface as
compile errors and would need the adapter complement. Guarded by `CrossPkgUser` (`namedLabel :
CrossPkgLib_package.Labeled`, passed to `CrossPkgLib.Describe`).

### Embedded-pointer hop receivers split per method
An interface member satisfied by promotion through an embedded POINTER field forwards through
the hop — but the receiver form depends on the target method: a `[GoRecv]` ref extension (or
struct method) binds the deref'd value (`this.File.Value.Name()`), while a **direct-ж primary**
(an extension on `ж<X>` emitted when the receiver escapes — os's `File.Read`/`Write`) binds the
box FIELD itself (`this.File.Read(p)`; deref'ing first strands the receiver, CS1929). The
generator discriminates by scanning the compilation for `this ж<X>` extensions — only
converter-emitted primaries are visible to the single-pass scan (sibling-generator ж-twins are
not), which is exactly the needed split. Applied to both the value-form partial and the pointer
adapter's hop arm. Guarded by `StructPointerPromotionWithInterface` (`Describer` over
`deviceHandle{*Device}`).

### With SEVERAL embedded pointers the hop is chosen per member, not per struct
The hop forwarding above was gated to a struct with exactly ONE embedded pointer, on the
reasoning that multi-embed interface satisfaction was rare. It is not: `net/rpc/jsonrpc`'s
`type pipe struct { *io.PipeReader; *io.PipeWriter }` (`all_test.go:310`) gets `Read` and
`Write` entirely by promotion from two different embeds. With the gate closed, every promoted
member fell through to the templates' bare receiver — `m_box.Read(p)` / `this.Read(p)` — which
binds nothing on the struct, so C# overload resolution reached the nearest same-named extension
anywhere in scope and reported **CS1929 naming a type the package never mentions**
(`io_package.Read(ref io_package.LimitedReader, slice<byte>)` from a jsonrpc test; likewise
`os_package.WriteString(ж<os_package.File>, …)`). That misdirection is the signature of this
defect — it reads as a missing reference and is not one.

The generator now indexes the hop path **per member** (`GetMultiEmbedHopPaths`), routing each
still-unbound interface member to the UNIQUE embed declaring it — which is precisely Go's
depth-1 promotion rule. A name TWO embeds declare is dropped rather than guessed (Go promotes
neither, so only a method the struct declares itself can satisfy the member — `*pipe.Close`
over the `Close` both `*io.PipeReader` and `*io.PipeWriter` declare). Each embed's method set
is read from local **syntax** where its type is declared in this compilation and from
**metadata** where it is not — a referenced assembly exposes both the converter's direct-ж
primaries and the public `RecvGenerator` ж-twins as ordinary symbols, which is the whole
jsonrpc case. The receiver form keeps the per-method split of the section above: a direct-ж
primary binds the embed's ж field itself (`m_box.Value.PipeReader.Read(p)`), anything else its
deref'd value (`m_box.Value.writer.Value.Write(p)`). Both emission paths are covered — the
pointer adapter and the value-form partial, since a pointer embed's method set is in the
STRUCT's method set too, so `var rw ReadWriter = p` (no `&`) records the pair as well. A member
neither resolution places is left unbound and keeps the old fallback, i.e. a loud CS1929 naming
it, never a silent wrong receiver.

Note this is *not* the same machinery as the `TypeGenerator`'s promoted-method forwarders,
which mint `M(this ж<Outer> …)` on the struct itself: those bail out on an embed with no local
declaration (`GetStructDeclaration` returns null for a metadata-only type), which is why a
struct with two LOCAL pointer embeds compiled all along and jsonrpc's two FOREIGN ones did not.
Guarded by `MultiPointerEmbedPromotion` (local embeds in both receiver forms, foreign embeds
`*strings.Reader`/`*strings.Builder` resolved from metadata, an overridden `Close` both embeds
declare, and pointer- and value-sourced casts of each, with write-through observed via the
original embedded objects, vs Go).

### A forwarded multi-value call deconstructs when tuple elements need interface conversion
`return newRawConn(f)` forwards a `(*rawConn, error)` tuple into a `(syscall.RawConn, error)`
result list — C# tuple conversions do not consult user conversions element-wise (CS0266). The
converter deconstructs into temps and converts each element through the usual interface
machinery (which also records the `GoImplement` pairing):

```csharp
var (ᴛ1, ᴛ2) = makeRelay();
return (new relayжReporter(ᴛ1), ᴛ2);
```

Elements whose actual type is itself an interface are left alone (structural inheritance
covers those). Guarded by `CrossPkgUser` (`getReporter` forwarding `makeRelay`).

### A multi-value call spread into a call's parameters in an assignment hoists into temps
Go lets a MULTI-VALUE call fill the parameters of an enclosing call — `r := t.newRange(t.parseControl("range"))`,
where `parseControl` returns five values feeding `newRange`'s five parameters. C# has no splat, so the inner
call is deconstructed into markers and passed expanded:

```csharp
var (ᴛ6, ᴛ7, ᴛ8, ᴛ9, ᴛ10) = Ꮡt.parseControl("range"u8);
var r = Ꮡt.newRange(ᴛ6, ᴛ7, ᴛ8, ᴛ9, ᴛ10);
```

`convExprList` already performs this expansion, but only when the call's `deferredDecls` hoist target is
non-nil — passing the whole tuple as one argument is otherwise CS7036 (text/template/parse's `rangeControl`).
The return-form threads that target (visitReturnStmt); the assignment forms do too, on BOTH lowering
branches: the single-declare block and the mixed/escaping block (a pointer-result local that is heap-boxed is
not counted in `declaredCount`, so it takes the latter — the `newRange` case above). A **statement-level**
`f(g())` (a bare expression statement, not an assignment) carries no `deferredDecls` of its own, so the
expansion now falls back to the enclosing `ExprStmt`'s `v.hoistedDecls` buffer — testing's
`registerCover2(deps.InitRuntimeCoverage())`, where `InitRuntimeCoverage` returns three values:

```csharp
var (ᴛ1, ᴛ2, ᴛ3) = deps.InitRuntimeCoverage();
registerCover2(ᴛ1, ᴛ2, ᴛ3);
```

The hoisted `var (…) = …;` lands in the statement's existing hoist buffer, emitted before the statement.
Byte-identical corpus-wide except where the pattern occurs (and a harmless renumber of any later temps, since
the per-file marker index is monotonic). Guarded by `TupleSpreadIntoCall` (a value result, an escaping pointer
result, and a statement-level spread).

A **PACKAGE-LEVEL var initializer** has no statement sink at all — `var debug = template.Must(
template.New("RPC debug").Parse(debugText))` (net/rpc debug.go; also internal/trace/traceviewer) passed the
whole `(ж<Template>, error)` tuple as `Must`'s one argument (CS7036). There the spill becomes a hidden
once-evaluated static tuple FIELD (`v.globalDeclHoist`, flushed by visitValueSpec before the var's own
field — C# static field initializers run in textual order, the same holder shape `visitPackageTupleVarSpec`
emits for `var a, b = f()`), and the arguments read its components:

```csharp
internal static (nint, nint) tupleᴛ1ʗ = parts();
internal static nint g = combine(tupleᴛ1ʗ.Item1, tupleᴛ1ʗ.Item2);
```

Guarded by the `TupleSpreadIntoCall` extension (a package-level `var` spreading a two-value call into a
wrapping call, value read back in main).

### A range over a pointer-typed type conversion parenthesizes before the deref
Ranging over a pointer to an array implicitly dereferences it — the converter appends `.Value` to the
range expression. When the range expression is itself a pointer-typed TYPE CONVERSION it renders as a C#
cast (`(ж<array<byte>>)(uintptr)(p)`, crypto/internal/nistec's p256 init over
`(*[43*32*2*4][8]byte)(*p256PrecomputedPtr)`). A cast binds LOWER than member access, so a bare append
`(ж<…>)(p).Value` parses as `(ж<…>)((p).Value)` — the deref lands on the operand, not the cast result
(CS1579 "no GetEnumerator" on the box type, CS8130). `visitRangeStmt` now wraps the range expression in
parentheses — `((ж<…>)(p)).Value` — whenever the pointer-unwrap deref is active and `rangeStmt.X` is a
`*ast.CallExpr` whose `Fun` is a type expression (`info.Types[Fun].IsType()`, which catches the
unsafe.Pointer conversions `isTypeConversion` deliberately excludes). Byte-identical corpus-wide (the
pattern only occurs on a pointer-producing conversion in range position, which never compiled before).
Guarded by `RangePointerArrayConversion` (transpile+compile+target only — the exact cast shape needs an
`unsafe.Pointer` source, whose runtime round-trip golib does not reproduce, so it is not output-compared).

### Adapter accessibility: symbol-OR-name on both sides
The adapter class scope cannot be derived from Go name casing alone (`error` is lowercase yet the golib interface is public METADATA - the name rule made io/fs's PathErrorжerror internal, CS0122 x40) nor from symbols alone (sibling generators' `public partial` modifiers are invisible to a single-pass generator - the symbol rule broke same-assembly interfaces like `CrossPkgLib.Reporter`). The ImplementGenerator takes symbol-OR-name on the struct AND the interface.

### GoImplement records de-duplicate at attribute emission
os converts dirEntry to fs.DirEntry both through its own alias (`type DirEntry = fs.DirEntry`) and through the io/fs name - two records for ONE interface made the generator emit the explicit implementation twice (CS8646/CS0111). The de-duplication happens at ATTRIBUTE EMISSION with the ALIASED record winning (its simple name resolves via the package usings); normalizing the RECORD KEY instead was twice wrong - qualified attr names break generator name resolution and flip the alias-locality gate. **Measurement lesson:** those declaration-phase errors had SUPPRESSED all of os's method-body diagnostics (Roslyn phase gating) - a package is not truly measured until its declaration errors are zero.

**The comparison must run on the EMITTED spelling, not the raw registry key (2026-08-08).** The
covered set is built from `exportedTypeAliases`, whose values `visitTypeSpec` has already
canonicalized — it reverts a file-local import rename before recording the alias target — while the
registry key keeps whatever rendering the cast site produced. os aliases its `io` import to `Δio`
(io is shadowed once io/fs is in the reference closure), so on the **unix** flavors
`unixDirent`→`fs.DirEntry` registers as `DirEntry` *and* as `Δio.fs_package.DirEntry`, and **neither**
compares equal to the canonical `io.fs_package.DirEntry` the covered set holds. Both records were
emitted for the one pair and `unixDirentжDirEntry` was composed twice (CS0102, CS0111 ×9, CS8646 ×4).

That also produced a **third** adapter spelling, worth recording because it looks like a separate
defect and is not: two records composing one adapter name is exactly what `adapterNameCollisionSet`
exists to detect, so it saw a FALSE collision, applied its collision-conditional rule and qualified
the foreign side of one cast site to `unixDirentжfs_DirEntry` — a name neither record produces
(CS0246). Removing the duplicate removes the collision, and all three call sites in `file_unix.cs`
converge. The key is now built with `qualifyLocalTypeRef`, the same canonicalization the emission
applies, so the two sides are comparable by construction. Windows is unaffected, and that was
measured rather than argued: its `os` registers the renamed-canonical spelling ALONE, with no
alias-keyed record, so nothing is covered and the qualified record is still the only record.
(Guarded by `implementRecordAliasCanonicalization_test.go`, whose controls pin both the Windows shape
and a genuinely distinct second implementation that must NOT collapse.)

**The interface-inheritance PRUNE exempts pairs that generate their own adapter CLASS.** The same
attribute-emission stage also drops a "lower" GoImplement record when the SAME implementing type is
recorded against a derived interface that C#-inherits it (elf's errorReader against both io.ReadSeeker
and io.Reader — the two value-form partial-struct implementations would implement `Read` twice,
CS0111/CS8646). That prune is only valid for the value-boxing PARTIAL-STRUCT form (one type, one
interface list). A pair whose implementation is a DISTINCT generated adapter class must survive, since
each cast site references the adapter for the EXACT interface it targets — the ж<T> pointer form was
already exempt, and the same now holds for the value-form adapter classes (`<src>ᴠ<iface>`): an
**interface-sourced** conversion (net/http wraps `net.Conn` values as `io.Reader`/`io.Writer` — the
prune dropped both pairs under the also-recorded `Conn→ReadWriteCloser`, so every
`new net_ConnᴠWriter(…)` referenced a class the generator never emitted, CS0246 ×17 in net/http and
recurring in net/rpc and httputil) and a **foreign-struct value** conversion
(`<pkg>_<T>ᴠ<iface>`) are marked at recording time (`adapterClassImplementations` in
`convertToInterfaceType`) and skipped by the prune. (Guarded by `IfaceToIfaceNarrow` — one source
interface converted to a full-surface embedded-interface target AND to its narrower bases at
argument, assignment, and return positions, dispatch output-compared vs Go.)

### Anonymous interfaces used as an adapter target are lifted package-wide

An inline anonymous interface used as a `GoImplement` target — internal/trace's `readBatch(r
interface{io.Reader; io.ByteReader})`, whose concrete `*bufio.Reader` argument is cast to the
inline interface — must resolve to a NAMED C# type on every side, or the raw Go structural
literal is emitted into the `package_info.cs` assembly attribute and into the adapter class
name (`bufio_ReaderжByteReader}` — the stray `}` breaks the C# parse and cascades ~75 syntax
errors across the file). `visitInterfaceType` already lifts the inline interface to a named
type (`readBatch_r`) in the visitor's per-file `liftedTypeMap`, but a cast at a DIFFERENT
file's call site (generation.go) has its own visitor and its own map, so `convertToInterfaceType`
saw only the raw `*types.Interface` and emitted the literal.

The lift is now also recorded in the package-level `packageDynamicTypeNames` registry — for
FUNCTION-scoped lifts too, since a function-parameter anon interface hoists to file level and is
referenced cross-file — exactly as anonymous structs already register (`visitStructType`).
`convertToInterfaceType` resolves an anonymous `*types.Interface` through the same three steps
`dynamicStructTypeName` uses: this file's `liftedTypeMap`, then the shared registry, then a
deferred `«DYNTYPE:…»` marker resolved after the file-visit barrier. The marker survives the
adapter-name composition (`adapterTypeRef`/`valueAdapterTypeRef` skip the simple-name strip when
it is present — the marker resolves as one unit to the already-simple lifted name), and the
`GoImplement` attribute writer resolves or drops it (mirroring the implicit-conversion writer).
`registerDynamicTypeName` keeps the lexically smallest name for a signature so the winner is
well-defined even when several files lift the same shape. Emitted form:
```csharp
// batch.cs (declaring file):
[GoType("dyn")] partial interface readBatch_r : /* io.Reader */ … { … }
// generation.cs (cross-file cast site):
(b, gen, var err) = readBatch(new bufio_ReaderжreadBatch_r(Ꮡr));
// package_info.cs:
[assembly: GoImplement<bufio_package.Reader, readBatch_r>(Pointer = true)]
```
Clears internal/trace's 75-error syntax cascade (the residual CS0315 — a named-numeric wrapper
not satisfying a lifted operator constraint — is a distinct, deeper root). Guarded by
`AnonInterfaceCrossFile` (a two-file package: file A declares `describe(thing interface{ Sizer;
Namer })`, file B casts a concrete `*box` to it — the lifted name must flow into the attribute,
the adapter, and the signature).

### An INITIALIZED var lifts its explicit anonymous declared type too — and a blank name lifts from the GO identifier

`visitValueSpec` lifts a var whose DECLARED type is an anonymous struct/interface literal, but
until 2026-08-09 only on the BODYLESS arm (`var x struct{…}`). Give the same var an
**initializer** and nothing lifted it, so the raw Go text landed in both the declaration type
and the value adapter's class name — and its braces close the C# member, making every following
declaration in the file read as a namespace-level one:

```go
// crypto/ecdh's test half opens with the documented-interface witness idiom:
var _ interface{ Equal(x crypto.PublicKey) bool } = &ecdh.PublicKey{}
```
```csharp
// before — CS1519/CS1002 at the site, then CS0106 on every remaining member, CS1022 at EOF:
internal static interface{Equal(x crypto.PublicKey) bool} _ᴛ1ʗ =
    new ecdhꓸPublicKeyжinterface{Equal(x crypto.PublicKey) bool}(Ꮡ(new ecdhꓸPublicKey(nil)));
// after:
[GoType("dyn")] partial interface _ᴛ1 { bool Equal(cryptoꓸPublicKey x); }
internal static _ᴛ1 _ᴛ1ʗ = new ecdh.ΔPublicKeyж_ᴛ1(Ꮡ(new ecdhꓸPublicKey(nil)));
```

The initialized arm now performs the bodyless arm's lift (both the struct and the interface
twin). Ordering is not a constraint: the adapter name is minted EARLIER in the same iteration by
`convertToInterfaceType`, but as a deferred `«DYNTYPE:…»` marker, so a lift registered afterwards
still resolves it at the file-visit barrier.

The lift is named from the **GO** identifier, not from `csIDName`. For an ordinary name the two
agree (`csIDName` is that name sanitized, and `getUniqueLiftedTypeName` re-sanitizes its
argument), but a BLANK `_` var's `csIDName` is a synthesized temp (`_ᴛ1ʗ`) that exists in no Go
scope — so `getUniqueLiftedTypeName`'s `typeExists` check cannot see it and hands the type the
field's own name back, giving one class a nested type and a field both called `_ᴛ1ʗ` (CS0102).
Passing `_` finds the blank var among the package's defs and bumps the type to `_ᴛ1`, distinct
by construction. (Guarded by the `AnonInterfaceVarWitness` behavioral test — two blank witnesses
over different anonymous interfaces, a NAMED anonymous-interface var that is then called through
its adapter, an anonymous-struct declared type, and a local interface value of the witness type,
output-compared vs Go; and by `crypto/ecdh`'s banked 47-verdict suite, which is where it was
found.)

### A collision-renamed type's pointer adapter composes on the package qualifier, never the whole-type alias

A COLLISION-RENAMED type resolves through a whole-type `global using` alias — `imageꓸRGBA` =
`go.image_package.ΔRGBA`, `ecdhꓸPublicKey` = `go.crypto.ecdh_package.ΔPublicKey` — which is a
single IDENTIFIER, not a qualified path. The adapter is a MEMBER of the declaring package's
class, so composing the adapter infix onto the alias names nothing: `imageꓸRGBAжImage`,
`ecdhꓸPublicKeyж_ᴛ1`, CS0246. The base is rebuilt as the file's package qualifier plus the
type's EMITTED simple name — what the declaring generator composed the class from — giving
`image.ΔRGBAжImage` / `ecdh.ΔPublicKeyж_ᴛ1`.

The FOREIGN-adapter arm carried this rebuild from the start; the SAME-ASSEMBLY arm (the `-tests`
production-under-test package, which compiles into the test assembly) did not, and the
asymmetry was invisible because it only bites a type that is BOTH collision-renamed and
adapted. crypto/ecdh shows both halves side by side: `PrivateKey` is not renamed, renders
`ecdh.PrivateKey`, and composed correctly all along, while `PublicKey` is renamed and did not.
Both arms now share `wholeTypeAliasAdapterBase`, which returns any render that already carries a
qualifier untouched — so it is a no-op for every un-renamed type. (The same-assembly arm is a
`-tests`-only shape, so its guard is `crypto/ecdh`'s banked suite rather than a behavioral
project; the foreign arm's twin is guarded by `CrossPkgUser`.)

### Every type-name render resolves a lifted anonymous struct cross-file

The registry/marker resolution above initially covered only two dedicated call sites
(`dynamicStructTypeName`'s `ж.of(…)` address-of-field form and `convertToInterfaceType`), while
the GENERAL type-name renderers — `getAliasQualifiedTypeName`/`getFullyQualifiedTypeName`, which every other emission
path reaches (heap-box declarations, casts, generic arguments…) — still fell through to raw
`t.String()` Go text on a `liftedTypeMap` miss. So ranging over a package-level anonymous-struct
slice declared in a SIBLING file, with the loop variable escaping to a heap box, stringified the
element type into the box declaration: bytes' `compareTests` (`[]struct{a, b []byte; i int}`,
declared in compare_test.go, ranged from the earlier-sorted bytes_test.go) emitted
`ref var tt = ref heap(new struct{a <>byte; b <>byte; i int}(), …)` — CS1526 plus a ~170-error
parser cascade that blocked all of bytes (Phase-4 blocker B8).

Both renderers now resolve a NON-EMPTY anonymous struct/interface through
`deferredDynamicTypeName` before the `t.String()` fall-through: the shared
`packageDynamicTypeNames` registry (the declaring file may already have been visited — file
visits run in deterministic sorted-file order), else the deferred `«DYNTYPE:…»` marker. The
empty `struct{}`/`interface{}` are excluded — their raw signatures intentionally map to
`EmptyStruct`/`any` downstream. The marker payload is now the HEX-ENCODED signature rather than
the raw text: these general render paths flow through string transformation passes
(`convertToCSTypeName` rewrites every `[`/`]` to `<`/`>`, alias handling splits on `.`) that
would corrupt an embedded raw signature before the post-barrier resolution could match it back
to the registry; hex digits pass through every transform untouched, and the encoding is a pure
function of the signature so equal signatures still render the identical (comparable) string.
Emitted form:

```csharp
// zvars.cs (declaring file, visited AFTER the reference):
[GoType("dyn")] partial struct compareTestsᴛ1 { … }
internal static slice<compareTestsᴛ1> compareTests = …;
// main.cs (cross-file range + heap box):
foreach (var (_, vᴛ1) in compareTests) {
    ref var tt = ref heap(new compareTestsᴛ1(), out var Ꮡtt);
    …
}
```

Guarded by `AnonStructCrossFile` (`zvars.go` declares `compareTests` and sorts after `main.go`,
forcing the marker path; `avars.go` declares `sizeTests` and sorts before it, taking the direct
registry hit — main.go ranges over both with `&tt`/`&st` forcing the heap box, output-compared vs
Go).

### A lifted type name is unique across the PACKAGE, and the `-tests` variant inherits production's

Resolution (above) is one half; **naming** is the other. Every lifted type — an anonymous
struct/interface, or a function-local declaration hoisted out of its body — is emitted as a
**nested type of the single `<pkg>_package` partial class**, so its name has to be unique across
the whole package. The uniquing set was per-FILE, which is a scope narrower than the emission
target: two sibling files whose lifts reach for the same generated name each believed the name
free and both declared it.

Both spellings a lift can start from are exposed to this. An anonymous type with no name of its
own falls back to the generic `type` (rendered `Δtype`, then `Δtypeᴛ1`, `Δtypeᴛ2`, … per
collision), and a function-local declaration is prefixed with the **method name only** — which
sibling files legitimately share, since Go allows one `probe` method per receiver type.
encoding/gob hit both at once: production `type.cs` and the internal-variant `encoder_test.cs`
each lifted a differently-shaped `struct{…}` to `Δtype`/`Δtypeᴛ1`, and the class then carried two
definitions of each — CS0579 on the doubled `[GoType]` attribute plus CS0111/CS0557 on every
member `go2cs-gen`'s `TypeGenerator` emitted for the duplicate (32 errors, the whole package
blocked). Note the failure is **not** avoided when the two anonymous structs happen to be
structurally identical: the second `[GoType("dyn")]` is still a duplicate attribute.

The claim set is therefore package-scoped (`packageLiftedTypeNames`, reset per package/variant),
with two deliberate exemptions:

- A `[module: GoManualConversion]` file **does not claim**. Its emission is redirected to a
  non-compiled `.cs.auto` review sibling, so a claim there would push a real file's type name to a
  higher ordinal for a declaration that never compiles. Those visitors keep the per-file set alone.
- The `-tests` **INTERNAL** variant is pre-seeded with the names the production conversion claimed
  (`productionLiftedTypeNames`). That variant emits its `_test.go` files into the production
  package class while the production `.cs` on disk are **not** regenerated, so those names are
  immutable and the test-side lift is the side that moves — the same production-pinned rule
  `testMethodRenames` applies to declarators and the Tier-C hoist seed applies to literal fields.
  The seed is the production run's live claim set, captured in `convertTestVariants` before the
  first variant's `resetPackageState` (production conversion runs moments earlier in the same
  process). The **EXTERNAL** variant is not seeded: its `<pkg>_test_package` is a separate class
  and may reuse every production name freely.

```csharp
// type.cs (production, pinned):        encoder_test.cs (internal variant, steps around):
[GoType("dyn")] partial struct Δtype {  [GoType("dyn")] partial struct Δtypeᴛ7 {
    internal nint r7;                       internal nint A;
}                                       }
```

Residual: two package-level anonymous structs that are structurally IDENTICAL but declared in
different files still lift to two distinct C# types (one Go type split in two) rather than
sharing one. That combination cannot compile today either — it is the CS0579 case above — so
nothing regressed; unifying them needs the second declaration's *emission* suppressed, not just
its name reused.

Guarded by `AnonStructCrossFile`'s `bvars.go`/`yvars.go` (both manifestations, straddling
`main.go` so file order is exercised in both directions) and, for the `-tests` seed,
`TestTestVariantPinsProductionLiftedTypeNames`.

### Function-literal parameters share the body scope
Go declares parameters in the function block, so a body-level `fpath, err := ...` REUSES a literal's `err` parameter. The variable analysis gives literals ONE merged scope (params + body declarations) mirroring real function declarations; a separate param scope had made the `:=` a shadow declaration beside later reuses (CS0841/CS0128, os CopyFS's WalkDir literal). Guarded by `LambdaFunctions` (`probe`).

### System-colliding local type names are root-qualified in assembly attributes
A Go package can name one of its own exported types after a top-level C# `System` type — internal/profile's `ValueType`, go/ast's `Object`, bytes' `Buffer`. The `GoImplement`/`GoImplicitConv` assembly attributes generated in `package_info.cs` sit at **file scope**, before the `namespace` line, where both `using System;` (a csproj global using) and `using static go.<pkg>_package;` are active — so a bare `ValueType` is ambiguous between `System.ValueType` and the package type (CS0104). The emitter root-qualifies any bare, dotless type name matching a curated set of `System` top-level names at the package class:

```csharp
[assembly: GoImplement<go.@internal.profile_package.ValueType, message>]
[assembly: GoImplicitConv<go.@internal.profile_package.ValueType, ж<go.@internal.profile_package.ValueType>>(Indirect = true)]
```

Foreign types are always package-qualified already (dotted) and are left untouched; no non-colliding name changes, so every non-colliding attribute emits byte-identically. (Guarded by the `SystemCollidingTypeName` behavioral test.)

### A name both `-tests` variant classes declare is qualified with the FILE's anchor class
The same file-scope ambiguity has a second source under `-tests`. The merged test metadata carries a
`using static` for the package under test (`<pkg>_package`) **and** for the external suite
(`<pkg>_test_package`) — the second one added so an attribute argument can name a type the external
test files declare (B3). A Go package is free to declare the same simple type name on both sides,
and encoding/gob does: `Point` and `Vector` are declared by `codec_test.go` (package `gob`) and
again by `example_encdec_test.go` / `example_interface_test.go` (package `gob_test`). The bare
reference then binds to neither — CS0104 ×3, which blocked the whole package build.

Such a name is emitted **class-qualified**, with the class the metadata FILE anchors to — its first
class, which is also what the `go2cs-gen` generators host output in:

```csharp
// package_test_info.cs — anchored at the production class:
[assembly: GoImplement<go.encoding.gob_package.Point, Squarer>]
[assembly: GoImplement<go.encoding.gob_package.Vector, Squarer>]
// package_info_external_test.cs — anchored at the external test class:
[assembly: GoImplement<go.encoding.gob_test_package.Point, Pythagoras>]
```

The anchor is a property of the file, **not** of the variant writing it: the external variant also
merges its production-anchored partition into `package_test_info.cs`, and a bare local reference
there still means the production class — the very invariant the B4/B5 record split already relies on
(`isTestAnchoredImplementRecord`: "a BARE impl name is a type declared in the external test package
itself"). Making the reference explicit states that invariant instead of assuming it. Because the
qualification runs LAST in the name pipeline — after `stripLocalTypeQualifier` — both arrival forms
(bare from the declaring variant, class-qualified then stripped from the other) converge on ONE
canonical spelling, so the merge HashSet still dedupes them to a single record.

The name set is computed once per `-tests` conversion from the two loaded variants and is empty
otherwise, so nothing outside `-tests` changes (check-no-regression: byte-identical across all 495
behavioral projects). Guarded by `TestAmbiguousVariantTypeNamesAreClassQualified`.

## Pointers
Pointer conversions use the golib heap box [`ж<T>`](https://github.com/ritchiecarroll/go2cs/blob/master/src/core/golib/%D0%B6.cs) (read "zhe"). Taking the address of a value uses the address-of operator `Ꮡ` (e.g. `Ꮡx`); an escaping local is allocated via `heap(...)`, and addresses of a struct field or array element are taken through `.of(Type.ᏑField)` / `.at<T>(index)`.

The box's value accessors follow one naming scheme (unified 2026-07-02; the checked accessor was previously `val`): **`Value`** is the strict dereference (`ref`-returning; panics on a nil pointer, as Go does), **`ValueSlot`** is its no-check twin (the identical real slot — for reads/writes of a held value that may legally be nil), and **`DerefOrNull()`** is the null-box-tolerant extension every pointer ENTRY alias binds through (an *extension method* is the only ref-returning form C# permits on a possibly-null receiver; it returns a NULL ref when nil, so the alias binds and the panic lands at the body's own deref). The same `Value` name is used by the generated named-type wrappers for their underlying-value accessor and by the golib `uintptr` struct for its raw word — converted code has exactly one spelling for "the value behind this thing". A Go struct **field** named `val` still emits as `.val` (it is the user's identifier, not the accessor):

```csharp
ref var a = ref heap(new array<@string>(2), out var Ꮡa);  // escaping local
var p = Ꮡa.at<@string>(0);                                 // &a[0]
var pField = Ꮡsettings.of(settingsᴛ1.ᏑRetries);            // &settings.Retries
```

**A heap-boxed *range variable*** needs the box allocated **per iteration**. When a `for i := range s` (or `for _, f := range s`) variable has its address taken, it escapes — but the foreach already declares that name, so a single `ref var i = ref heap(…)` before the loop would clash (CS0136). The converter iterates a *temp* and, inside the body, allocates a fresh box each pass and copies the temp into it:

```go
for i := range s {
    p := &i      // i escapes
    use(p)
}
```
```csharp
foreach (var (iᴛ1, _) in s) {
    ref var i = ref heap(new nint(), out var Ꮡi);   // a FRESH box each iteration
    i = iᴛ1;
    var p = Ꮡi;
    use(p);
}
```

The per-iteration box is required for Go 1.22 loop-variable semantics: each iteration's variable is distinct, so a stored `&i` must point to a different box each pass (`for i := range s { ptrs = append(ptrs, &i) }` yields `0 1 2`, not `2 2 2`). A non-escaping companion variable still declares directly in the foreach. (Guarded by the `RangeVarHeapBox` behavioral test — both a within-iteration `&i` and the stored-pointer distinctness case; runtime exercises it in `for i := range stackpool` and `for _, f := range s.Fields`.) A heap-boxed `for i := …; cond; post` **clause** variable takes the same per-iteration box through its carrier rewrite — see [*For-clause variables are per-iteration*](#for-clause-variables-are-per-iteration-go-122-loop-variable-semantics).

The `at<T>(index)` element-address accessor takes a `nint` index. Go permits **any** integer type as an array/slice index and converts it to `int` for the access, but C# has no implicit `nuint`/`uint`/`ulong`→`nint` conversion, so a non-`int` index is narrowed explicitly to match Go's index-to-int conversion (CS1503 otherwise). An `int` index, or an untyped int constant (which renders as a plain int literal), is emitted as-is:

```csharp
var pi = Ꮡa.at<nint>((nint)(i));        // &a[i]      where i is a uintptr
var pe = Ꮡa.at<nint>((nint)(g % 2));    // &a[g%2]    where g is a uint (g%2 widens to long in C#)
```

This is the element-address analogue of the indexed-literal key cast (`SparseArray<T>`, above) and the `IBinaryInteger<T>` width-agnostic length params on `unsafe.Add`/`Slice`/`String`. (Guarded by the `ArrayWideIndexAddress` behavioral test.)

A **string** indexed by a wide/unsigned integer takes the same `(int)` cast: a string LITERAL renders as a `ReadOnlySpan<byte>` (`"…"u8`) whose indexer takes `int`, so a `uintptr` index is CS1503 — runtime `heapdump.go`'s `"0123456789abcdef"[pc&15]` emitted `"…"u8[(uintptr)(pc & 15)]`. The index-expression emission routes a wide-kind index on any string-typed base through the cast — `"…"u8[(int)((uintptr)(pc & 15))]` — and an `@string` *variable*'s indexer binds an `int` argument too, so both renders are covered; an int/small index is unchanged. (Guarded by the `ArrayWideIndexAddress` extension — literal and variable string bases with `uintptr`/`uint64` indexes, byte values vs Go.)

The address of a **slice** element uses the call form `Ꮡ(slice, index)` (golib overloads `Ꮡ<T>(IArray<T>, int)` and `(…, nint)`) rather than `at<T>`. Go `int` (→ `nint`) and the small integer types that implicitly widen to `int` bind directly, but an unsigned-32-or-wider or 64-bit index (`uint`/`uint32`/`uint64`/`uintptr`/`int64`) binds neither overload, so it is cast to `int`: `Ꮡ(s, (int)(i))`. Only those wide/unsigned types are cast — an `int`/`nint` or small-int index is emitted as-is to avoid churn. (Mirrors the runtime's `&datap.pclntable[funcoff]` / `&filetab[fileoff]`, indexed by `uint32` offsets. Guarded by the `ElementAddressUnsignedIndex` behavioral test.)

The `Ꮡ(slice, index)` form applies to **any slice-typed base expression**, not just a named slice variable — a method-**call** result (`&b.stk()[0]`, runtime `mprof.go`; `&StringByteSlice(s)[0]`, `syscall`), a builtin/`make` result, an `unsafe.Slice(…)` result (`reflect`), or a **slice-expression** base (`&x[0:cap(x)][cap(x)-1]`, `math/big`). Such bases have no bare identifier, so they previously fell out of the (identifier-gated) slice arm into the *array* branch — a slice's type name also starts with `[` — whose naive fallback textually prefixed `Ꮡ` onto the postfix chain: `Ꮡb.stk().at<uintptr>(0)` binds as `(Ꮡb).stk()…`, referencing a box that does not exist (CS0103), or copy-boxed the slice header (a lost-write latent). The element address of the returned slice **view** reaches the shared backing array per Go aliasing, so a write through the pointer is visible via the original storage. (Guarded by the `NestedFieldElementAddr` extension — `&st.stk()[0]` through a pointer local, write-through vs Go.)

The same `(int)` narrowing (the shared `castWideIntegerToInt` helper) applies to the bounds of a **3-index (full) slice** `s[low:high:max]`, which lowers to the golib `.slice(nint low, nint high, nint max)` method: a `uintptr`/`uint`/`uint32`/`uint64`/`int64` bound is cast — `stk[:b.nstk:b.nstk]` (b.nstk a `uintptr`) → `stk.slice(-1, (int)(b.nstk), (int)(b.nstk))`. Go's own slice bounds are `int`, so the narrowing matches Go. A plain `int`/small-int bound is left uncast. (The 2-index range forms `s[lo:hi]` narrow through `getRangeIndexer` for the C# `[..]` range operator; only the 3-index `.slice()` form needed this.) (Guarded by the `Slice3IndexWideBound` behavioral test — `uintptr`/`uint`/`uint64` full-slice bounds on an array and a slice + an int control, values verified vs Go; runtime hits this in `mprof`'s `stk[:b.nstk:b.nstk]`.)

**Address of an element of an array *field* reached through a pointer or boxed struct.** When the array being indexed is a field of a heap-boxed value — `&mp.future[i]` where `mp` is a `*memRecord`, or `&g.future[i]` where `g` is an address-taken global — the *array field's* address goes through the box-field accessor first, then the element index: `Ꮡmp.of(memRecord.Ꮡfuture).at<cycle>(i)` (pointer parameter), `mp.of(...)` (pointer local), `Ꮡg.of(rec.Ꮡfuture).at<cycle>(i)` (boxed global). A naive `Ꮡ` prefix on the field read (`Ꮡ(~mp).future`) instead binds `.future` to the box value `Ꮡ(~mp)` (a `ж<memRecord>`, which has no `future` member) → CS1061. This requires a matching golib detail: `ж<T>.at<TElem>(index)` resolves the array through the `Value` property, **not** the raw `m_val` field — for a field-reference pointer produced by `of(...)`, `m_val` is an empty default and the real array lives behind `Value` (the same resolution `of(...)` itself uses). Reading `m_val` would miss the array → null-deref at runtime even though the C# compiled. (`array<T>` is a readonly struct over a shared backing `T[]`, so the value `Value` yields still aliases the real elements; writes through the returned element pointer land.) (Guarded by the `PointerFieldArrayElementAddress` behavioral test — pointer parameter and pointer local both taking `&p.future[i]` and mutating through it.)

The RECEIVER's own array field is the one case that does **not** take this route: a Go pointer receiver renders as `this ref T recv`, which has no box companion, so `of(...)` would name a box that does not exist (`Ꮡr.of(RegArgs.ᏑInts)`, CS0103). It uses the element-aliasing two-arg `Ꮡ(recv.field, (int)(i))` instead — correct because copying an `array<T>` wrapper shares its backing `T[]`. See *Element address of an ARRAY FIELD of the receiver* under Slices and Arrays for the write-dropping bug this replaced (`compress/flate` losing all LZ77 matching).

### A colliding pointer-adapter name qualifies its FOREIGN interface side

A pointer-interface adapter class is named `[<pkg>_]<structSimple>ж<ifaceSimple>`. The STRUCT side is
package-qualified when foreign (`bytes_ReaderжReader`), which keeps two same-named foreign structs
adapting to one interface apart. The INTERFACE side had no such treatment — it composed from its bare
last-dot segment — so the mirror-image case collided: ONE struct cast to TWO interfaces whose simple
names match composes one class name twice (`CS0102`, `CS0111` per member, `CS8646`).

`compress/flate` is the case that surfaced it. It declares its own `Reader` (`io.Reader` +
`io.ByteReader`), and its tests hand a `*bufio.Reader` and a `*bytes.Reader` to `NewReader`, which
casts to both that and `io.Reader` — so `bufio_ReaderжReader` and `bytes_ReaderжReader` were each
emitted twice and the package could not build its test host at all. The rule is
**collision-conditional**: only within a group of records composing the same name does the interface
side take a package qualifier (`bufio_Readerжio_Reader`), and the LOCAL member of a group keeps the
bare name (at most one member can be local, so that stays unambiguous and preserves the Go-like short
form). Qualifying unconditionally was measured and rejected — 644 distinct adapter names across 3,688
construction sites would churn. The entire 302-package production corpus contains **no** collisions,
so the rule is byte-neutral there by construction; it takes a test closure's extra casts to make one.

Grouping keys on the whole composed name, struct side included. `compress/gzip` records both
`<Reader, io.Reader>` and `<bufio.Reader, flate.Reader>` — two records whose interfaces share a simple
name but whose struct sides differ, composing `ReaderжReader` and `bufio_ReaderжReader`. Keying on the
interface name alone would call that a collision and rename a validated package's adapters for nothing.

The converter and the generator must agree on every name, and neither may guess, so both read the same
authority: the final `[assembly: GoImplement<…>(Pointer = true)]` lines. That set is not known while
cast sites are being rendered — it is settled only after the whole package is visited and
`writePackageInfoFile` has applied its alias-covered skip and its interface-inheritance prune — so a
cast emits a deferred marker (mirroring the DYNTYPE marker of the anonymous-struct barrier) that
`resolveAdapterNameMarkers` rewrites once the records are final. The marker's payload is hex-encoded
for the same reason DYNTYPE's is: a rendered type name passes through string transformation passes
before reaching the file. Only the INTERFACE side is ever rewritten — the struct side is emitted
verbatim, because it is the reference's *path*, not just a name fragment: rewriting it turned
`new os.FileжWriter(f)` (namespace `os`, adapter class `FileжWriter`, generated in os's own assembly)
into a bare `FileжWriter` that resolves nowhere. (Guarded by `AdapterNameInterfaceCollision` — a local
`Reader` and `io.Reader` reached from one `*src`, verified by reverting the fix: `CS0102` + `CS8646`
on `srcжReader`. Unblocked `compress/flate`'s Phase-4 test host.)

The same `Value`-not-`m_val` rule applies to the **dereference operator** `~`. A value read through a pointer — `(~c).field`, the form the converter emits for `c.field` where `c` is a `*T` — must resolve through `Value`. For a *field-reference* pointer (`c := &b.w` → `Ꮡb.of(box.Ꮡw)`) or an array-element pointer, the real storage lives behind `Value` and `m_val` is an empty default, so `operator ~` returning `m_val` would read a **zero-valued copy** (`(~c).a` → `0`) — it compiles but is silently wrong. `ж<T>.operator ~` therefore returns `value.Value` (which resolves struct-field / array-element references and, for a standard pointer, is exactly `m_val`), matching the `IPointer<T>.operator ~` that already did. This surfaced when a defined-type-over-struct's forwarded fields were read back through a `*wrapper`, but it is general to any `*x.field` value read. (Guarded by the `NamedTypeOverStruct` behavioral test's read-back path.)

The `at<E>(i)` **element type `E` is rendered fully-qualified** — `at<sync.atomic_package.Int32>`, not the file-local alias `at<atomic.Int32>`. A namespace-rooted type resolves inside `namespace go;` without any `using <pkg>` alias, whereas the alias form needs the file to import that package. A file can index a cross-package-typed array field of a struct without ever naming the element type (so Go requires no import, and the converter emits no `using atomic`), which would leave the alias unresolved (CS0246, e.g. runtime's `tracecpu.go` indexing `trace.cpuLogWrite`). A current-package or basic element renders identically either way, so this is churn-free. (Guarded by the `ArrayOfCrossPackageType` behavioral test's `&x.c[i]` element-address case.)

Using `ж<T>` rather than the C# `ref` keyword avoids the escape-analysis complications of passing a `ref` into code that expects a heap-allocated pointer. This is a simplification that can cost an unnecessary heap allocation when an address is taken; a future escape-analysis pass could keep such values on the stack when it is provably safe, similar to how [Go does this](https://golang.org/doc/faq#stack_or_heap) at compile time.

> Note: a package-level global whose address is taken is backed by a real heap box so that writes through `&global` (and `&global.field`) are observed, rather than mutating a copy.

### Pointer equality canonicalizes the STORAGE, not the referent — slice/array element identity

Go compares pointers by **address**: `unsafe.StringData(s) == unsafe.StringData(t)` is true whenever both strings share the same backing data (a header copy `t := s`, or `strings.Map`'s identity fast path returning `s` unchanged — strings' `TestMap` asserts exactly that). `ж<T>.Equals` already models address identity per referent shape — struct-field refs compare (source, field-identity), array-index refs compare (backing, index), heap boxes compare wrapped-object identity — but the array-index arm compared the `IArray` *instance*, and `@string.buffer` materializes a **fresh `PinnedBuffer` view per access**, so two `StringData` results over the very same bytes never compared equal ("unexpected copy during identity map"). The array-index arm (and the matching `GetHashCode`) now canonicalizes a `PinnedBuffer` to the object its `GCHandle` pins (`PinnedTarget`, normally the string's backing `byte[]`) before the reference comparison, so equal addresses compare equal while everything previously-equal stays equal — the canonicalization only ever *adds* true results for same-storage-same-index pairs, and distinct-but-equal arrays still compare unequal (Go pointer semantics, never value comparison). `strings.Map`'s fast path needed no change at all: it already returned `s`, sharing the backing array through the `@string` struct copy — only the identity *comparison* was blind. (Guarded by the `StringDataIdentity` behavioral output test — header-copy identity true, repeated-call identity true, a runtime copy false, content equality unaffected; before the fix the two identity cases printed `false`.)

**The same class covers ORDINARY slice and array element pointers, which were the larger miss (2026-07-24).** `Ꮡ(target, index)` takes an `IArray<T>`, an INTERFACE, so passing a `slice<T>` — a HEADER (backing array + low + len + cap) over storage it merely *shares* — **boxes the header struct afresh on every call**. The array-index arm compared those boxes, so `&s[0] == &s[0]` was **false**: Go's most basic pointer identity, violated for every slice element. The blast radius is wider than comparison alone, because `GetHashCode` canonicalized the same way: two aliasing element pointers landed in different `map[*T]` buckets, so `m[&s[2]] = "two"` then `m[&s[2]] = "TWO"` *added a second entry* instead of overwriting, and `m[&s[2]]` read back the zero value. `array<T>` (and the generated named-array wrappers) had the identical problem, reached through `ж<T>.at` (`&a[i]` on a boxed array), which likewise boxes a copy of the wrapper struct.

`CanonicalStorage(IArray)` is now `CanonicalElement(IArray, index)`, returning the **actual storage object plus the ABSOLUTE element index within it** — the referent as stored is never the storage itself:

| Referent | Canonical storage | Index |
|---|---|---|
| `PinnedBuffer` (per-access view) | the object its `GCHandle` pins (`PinnedTarget`) | unchanged |
| `slice<T>` | its backing `T[]` (`m_array`) | `Low + index` |
| a named slice type | the backing of the `slice<T>` its full-window interface sub-slice hands back | `Low + index` |
| `array<T>`, named-array wrapper | the raw backing via non-generic `IArray.Source` | unchanged |
| any foreign `IArray` | the referent itself (prior behavior) | unchanged |

Folding the window offset into the index is what makes every Go alias of one element compare equal, not just the same-header case: a re-slice (`&s[1:][0] == &s[1]`), a re-slice of a re-slice, an in-capacity `append` result (`&append(s[:0:1], 9)[0] == &s[0]`), and a slice over an array (`&a[:][i] == &a[i]`) all reduce to the same `(T[], absolute index)` pair. A named slice type wraps a `slice<T>` it does not expose, so it is unwrapped via `view.Slice(0, view.Length)` — the same trick `slice<T>`'s `ISlice<T>` constructor uses; `Source` cannot serve, because a slice header's `Source` deliberately materializes a **detached copy** (only `array<T>`'s is raw).

Canonicalizing `array<T>` to its backing is sound precisely because Go's by-value array COPY is emitted as golib's `.Clone()` (see [Slices and Arrays](#slices-and-arrays)), giving the copy its own backing — two distinct Go arrays can never canonicalize to the same storage. Like the `PinnedBuffer` precedent, the change only ever *adds* true results for same-storage-same-index pairs: distinct backings still compare unequal (`&z[0] != &s[0]`), distinct indices still compare unequal (`&s[0] != &s[1]`), and the struct-field and heap-box arms are untouched. This is a golib-only change — no emitted-code difference.

(Guarded by the `SlicePointerIdentity` behavioral output test — self identity, distinctness, re-slice and re-slice-of-re-slice aliasing, the in-capacity `append` result, array-vs-slice-over-that-array, struct elements, a write through an element pointer observed through both views, and `map[*int]` store/overwrite/lookup/miss including a lookup keyed through a *different* window — vs `go run`. Counter-proven: pre-fix **every** identity assertion printed `false`, both map lookups returned empty, and `len(m)` grew from 2 to 3 on the overwrite.)

The same "a box is a temporary, the storage is the object" reasoning answers **lifetime** questions —
when the referent dies, and whether two boxes name the same allocation — which `runtime.SetFinalizer`
and `sync.Cond`'s copy detector both depend on, and which is also why `Ꮡ(IArray<T>, index)` must take
its target **by value**: see
[A pointer's REFERENT, not its box, answers every lifetime and identity question](#a-pointers-referent-not-its-box-answers-every-lifetime-and-identity-question).

### A pointer's nilness and identity are STRUCTURAL — the `IsNull` / `IsNilPointer` split

`ж<T>` answers two different questions that a single predicate used to conflate, and the conflation was a defect *class*: `IsNull` was `m_isNull || m_val is null`, so it reported **true** for three unrelated things —

1. **THE nil pointer** (`m_isNull`: nil-constructed, or the canonical `NilBox`) — the only one that is actually nil;
2. **a real address whose reference-typed pointee is legitimately nil** — `&i` with `i == nil`, `new(any)`, a closure-captured `p *T` local (`ж<ж<T>>`). In Go these are ordinary non-nil addresses, and `*p` yields the nil *value* rather than panicking;
3. **a struct-field or array-element reference box** over a reference-typed `T` — `&s.next`, `&elems[i]` — whose storage lives in the referenced struct/array, leaving `m_val` an unused `default` that reads as null for any reference type. A perfectly valid address, misread as nil purely because of where its storage is.

`IsNilPointer` (`m_isNull`) is the STRUCTURAL predicate, and everything about pointer **identity** keys off it — equality, `GetHashCode`, `PointerOrderToken`, the reflection bridge's `IsNil`/`Elem`, and the dereference guard on `operator ~`. `IsNull` keeps only case 2 (case 3 is now excluded structurally, alongside the native-address exclusion added earlier for the same reason), and is consulted only where peeking at the value is the actual question: the strict `Value` getter, `PinnedBuffer`, and the `uintptr`/`void*` address conversions — where a reference-typed pointee has no reportable address at all, so `0`/`null` is the only answer the managed model can give, and for the value-typed pointees that actually cross into native code the two predicates coincide (`unsafe.Pointer`'s pointee is `uintptr`, so every `IsNull` in that class *is* the structural question).

Fixed consumers: `operator ~` (both the `ж<T>` and the `IPointer<T>` interface form) guard on the structural predicate and read `ValueSlot`, so `*p` on a real address whose pointee is nil yields nil — and a *field-reference* deref reads the field instead of throwing; `DerefOrNil`/`IsNilStandardPointer` likewise, so the pointer-walk re-alias hands back the **real** slot (a write through it persists) rather than the throwaway; and the reflection bridge's interface-routed slot read (`GoReflect.readSlotViaInterface`) and `deepValueEqual`'s cycle-detection `identityRoot` ask `INilPointer.IsNilPointer` instead of the value-peeking property — the latter previously dropped `&i`-shaped values out of cycle detection entirely.

**Two identity rules changed with it.** (a) A standard heap box's identity was formerly *derived from the value it held* whenever `T` was a reference type — two distinct boxes wrapping one referent compared **equal**. That reported `&c == &d` **true** for two distinct `*int` variables holding the same pointer, collapsed `map[**int]V{&c: …, &d: …}` into a **single** entry, and made a pointer's **hash mutate when its pointee was assigned**, so a key inserted while its pointee was nil could never be found again (`m[q]` read back the zero value while `len(m)` still said 1). A pointer's identity is its storage: a standard box *is* the storage, so it hashes and compares by its own identity, and `&x == &x` holds because an addressed Go variable is heap-boxed **once**. (b) Conversely, two boxes **aliasing the same native address** (`m_nativeAddr`) are now the same pointer — a `uintptr` round-trip mints a fresh box each time, and Go requires `(*T)(unsafe.Pointer(p)) == (*T)(unsafe.Pointer(p))`.

golib-only change — no emitted-code difference. (Guarded two ways, because the converter routes every reference-typed-pointee deref through `.ValueSlot` — verified with a 6-shape probe including generics — so `operator ~` is unreachable from converted Go and only golib-internal, hand-owned and reflection-bridge code takes it. The Go-expressible half is in the `PointerToNilPointerIdentity` behavioral output test: distinct-variable identity, `map[**int]` two-key distinctness, hash stability across a pointee assignment, and reference-typed field-reference deref + map keying — pre-fix `&c == &d` printed `true`, the two-key map held **1** entry, and the stable-key lookup read back empty. The unreachable half is in `GolibTests.PointerNilPredicateTests`, which drives `operator ~` (both forms), `DerefOrNil`, `ReadPointerSlot` through a hand-written stand-in for a generated named-pointer wrapper, and native-alias identity — 7 of its 10 assertions fail pre-fix.)

### Reading a pointer and taking a field pointer allocate NOTHING — the two costs hidden inside `ж<T>`

Go's `*p` and `&x.f` are free. Both allocated in go2cs, silently — the code was correct, it merely paid — and the bill was visible only where something counted it. `os.TestWriteStringAlloc` bounds `f.WriteString(s)` at **zero** allocations; the measured cost was over nine thousand bytes per call (**9,184** through the test pipeline, **9,208** under the standalone probe, which writes to its own file rather than the host's `t.TempDir()` one), and a byte-exact decomposition of the probe's number (markers around every frame of `WriteString → File.Write → poll.FD.Write → syscall.Write`, arithmetic closing to the byte) put **5,728 of it — 62 %** in these two places, not in the defer machinery that was the standing suspicion (the frame for that shape is 192 bytes, near 2 %).

**1. `IsNull` boxed the whole pointee on every dereference — 4,760 bytes (52 %).** `Value`'s standard-box branch guards on `IsNull`, whose last term is the value-peeking `m_val is null` (case 2 of the split above — a real address whose reference-typed pointee is legitimately nil). On an **unconstrained** type parameter `is null` compiles to `box !T; ldnull; ceq`, so a term that is constant-false for every struct `T` still allocated *and memcpy'd* a full copy of the pointee, on every read. A pointer to a large record paid its own size per dereference: `os.file` is 592 bytes, and the write path walks eight `of()` links, each bottoming out in one of these. The term is now guarded by a per-`T` `s_valueCanBeNull` (`!typeof(T).IsValueType || Nullable.GetUnderlyingType(typeof(T)) is not null`), computed from the type rather than by boxing `default(T)`, so type initialization allocates nothing either. The guard also let the peek read the RIGHT storage: a `T` containing no references keeps its value in the pinnable `m_slot` and leaves `m_val` the unused default, so `m_val is null` answered for the wrong slot and every `ж<Nullable<T>>` reported nil whatever it held — unreachable from converted code (Go has no `Nullable`), and wrong, so it is corrected alongside.

**2. `of(…)` minted the untyped accessor wrapper per CALL — 968 bytes (11 %).** `of<TElem>` stores an `object`-taking wrapper around the typed field accessor. The wrapper closes over nothing but that accessor, so it is a pure function OF it — and the accessor is a static method group, which the compiler already caches to a singleton. Minting the wrapper per call therefore bought a fresh display class plus a fresh delegate (88 bytes) for a value identical every time, on every `&x.field` in the corpus. It is now memoized per accessor in a `ConditionalWeakTable`; the keys are weak, so an accessor that is genuinely per-call leaves no permanent entry. Pointer equality is unaffected — it compares the field IDENTITY token (the original accessor), which is what made the per-call wrapper tolerable in the first place.

Together these take `os.File.WriteString` from **9,208 to 3,168 bytes per call (−65.6 %)** — probe and pipeline agreeing to the byte afterwards, the test now printing `expected 0 allocs for File.WriteString, got 3168` — and the same two costs were being paid by every pointer read and every field address in every converted package. The row still does not reach zero — the remainder is the `ж<T>` boxes themselves (1,488 B, of which 608 is one `ж<FD>` whose inline `m_val` slot a field reference never uses), the syscall seam's `unsafe.Pointer`/`heap` boxes (784 B), the defer machinery (192 B — the display class and delegate of each capturing defer) and the `unsafe.StringData` pin (136 B) — inherent to the current pointer and defer models rather than waste inside them. The arc for those is recorded in [`docs/phase4/BOARD-next-validation-candidates.md`](phase4/BOARD-next-validation-candidates.md).

golib-only change — no emitted-code difference. (Guarded by `GolibTests.PointerDereferenceAllocationTests`: four measured-byte assertions plus a semantics pair. With the fixes neutered they report 528 B/deref for a 512-byte pointee, 288 B/deref for a reference-bearing one, 32 B/deref through a field-pointer chain, and 200-vs-112 B/call for `of(…)` against a bare box of the same type — the last stated as a COMPARISON rather than a byte count so it survives any future change to `ж<T>`'s layout.)

### A pointer parameter whose every use is a dereference is a `ref` parameter — the ж-box ref-lowering

The emitted-form rule (stage A2 of [`docs/phase4/DESIGN-zh-box-reduction.md`](phase4/DESIGN-zh-box-reduction.md), rulings §10.1/§10.3/§10.4): an **unexported package-level function's** pointer parameter whose every body use is a dereference (`*p`, `p.f`, `p[i]`, `range p`), a derived address feeding another lowered position (`&p.f` → a lowered argument), or a forward into another lowered position, emits as a C# **`ref T` parameter** instead of the boxed `ж<T>` — and every call site passes a `ref` expression instead of minting or carrying a box. A `ref T` argument is an alias into the caller's storage the GC tracks and updates, so no pinning, no box, no allocation, and writes through it land in the caller's storage by construction. The signature reads as Go's `*T`, `ref` reads as Go's `&`, and the entry deref preamble disappears because the parameter *is* the alias:

```go
func p224Sub(out1, arg1, arg2 *p224MontgomeryDomainFieldElement) { ... }
p224Sub(&e.x, &t1.x, &t2.x)
```

```csharp
internal static void p224Sub(ref p224MontgomeryDomainFieldElement out1, ref p224MontgomeryDomainFieldElement arg1, ref p224MontgomeryDomainFieldElement arg2) { ... }
p224Sub(ref nonnil(ref e).x, ref nonnil(ref t1).x, ref nonnil(ref t2).x);
```

**What disqualifies (the whitelist argument — any use the classifier does not positively recognize keeps the box):** pointer identity or nilness (`p == nil`, map keys), escapes (returned, stored, captured by a closure or a defer/go argument frame), representation observations (`unsafe.Pointer(p)`, `uintptr(p)`, interface conversions, a method call ON `p`), re-pointing (`p = q`), and function-identity escapes (exported [Phase A], func-value uses, `//go:linkname` registry membership, named pointer types, bodiless assembly stubs, declaration in — or a curated call from — a `[module: GoManualConversion]` hand-owned file). Blank/unnamed pointer parameters are never candidates (no uses, nothing to gain, and the boxed path owns the synthesized-name conventions). The fixed point is two-sided: a call site whose argument shape has no `ref` emission row (including the tuple-splat `f(g())` form) strips the position rather than dead-ending emission.

**The call-site emission rows** (each self-checks and falls back to today's boxed emission wrapped `.DerefOrNull()` — total over classifier-admitted shapes without coverage ever being a soundness premise):

| Go argument | lowered emission |
|:--|:--|
| `&e.x` / `&p[i]` (base is a pointer's deref alias or a lowered `ref` param) | `ref nonnil(ref e).x` / `ref nonnil(ref p)[i]` |
| `&x.f` / `&s[i]` (value-rooted base: local, value param, global, slice) | `ref x.f` / `ref s[i]` — `nonnil` elided, the base cannot be null |
| `&x` (address-taken local/param/result — reverted or kept-box) | `ref x` (the plain local, or the entry ref alias into the surviving box — same storage either way) |
| a pointer variable/field/deref/assert (carries a box) | `ref (q).DerefOrNull()` — reads the box at CALL time, so a re-pointed pointer is never stale |
| a lowered `ref` parameter forwarded | `ref p` — it already is the ref |
| `(*T2)(&v.x)` (the named-array-wrapper reinterpret — §10.3's hoisted-temp rule) | `var ᴛ1 = nonnil(ref v).x.Value;` … `ref ᴛ1` — the wrapper's `Value` yields its `array<T>` header, a copy whose `T[]` backing is SHARED, so element writes flow through and whole-header writes are lost in both emissions equally (byte-parity with the old `Ꮡ((Ꮡv.of(…)).Value.Value)` form). Go requires identical underlying types for pointer conversions, so the wrapper family closes under `.Value` reads and single user-defined conversions; anything else (e.g. a named-SLICE reinterpret) keeps the boxed fallback |
| `&T{…}` composite literal | `var ᴛ1 = new T(…);` … `ref ᴛ1` — observationally identical to a distinct heap box, since a lowered callee can never compare, store, escape or convert the address |
| the literal `nil` | `ref ((ж<T>)default!).DerefOrNull()` — binds the null ref; the callee's first use faults with Go's panic |

**Address-taken locals revert for free.** A local (or value parameter, or named result) whose EVERY address-connected use feeds a lowered position — directly, outside defer/go, outside any closure — loses its `heap()` box entirely: the declaration reverts to a plain local, removing **two** counted objects per unmanaged local (the box and its eager pinnable slot). Any surviving box use (a stored address, a closure crossing, a pointer-receiver method) keeps the box, and the lowered sites alias the same storage through the entry ref alias. The reversion also collapses the per-iteration loop-variable boxing scaffold where the loop var's address only feeds lowered positions (`ForVariants`).

**The nil doctrine (ruling §10.4).** Go panics eagerly at `&e.x` when `e` is nil — before the callee is entered. A naive null byref would instead let the callee run side effects Go never runs and let a callee `recover` catch a panic it can never catch (the design review's S-F1 third behavior). Lowered field/element address formation over a *nullable* base (a pointer's deref alias — null exactly when the pointer is nil) is therefore eagerly checked by golib's `nonnil(ref e)` — one branch, zero allocation, throwing the exact panic `ж<T>.Value` raises — and elided where the base provably cannot be null (a value local/parameter/result, an addressed global's ref property). A plain nil pointer ARGUMENT (`f(q)` with nil `q`) still enters the callee and faults at first use, exactly as Go. Measured gc subtlety recorded with the guard: Go evaluates sibling function CALLS among the arguments in lexical order *before* non-call operands like `&e.x`, so "later arguments unevaluated" holds only for non-call operands.

**`defer f(&x)` / `go f(&x)` are boxed sites, categorically.** The defer/go machinery stores eagerly-evaluated argument values in a frame, and a managed `ref` cannot be stored there (the compiling alternative — a copy-box — silently loses writes: the panel's 0-vs-7 refutation). The eager arguments keep the boxed emission, the statement always takes the temp-param lambda form (a `ref`-parameter method group cannot convert to `Action<…>`), and the thunk derives each ref at invoke time: `defer(ᴛ1 => setErr(ref ᴛ1.DerefOrNull()), Ꮡerr, ref ᒐ);` — preserving Go's defer-time argument evaluation. An address flowing to a lowered position under defer/go keeps its box (the locals carve-out), and an address-carrying use of a candidate's OWN parameter inside a defer/go argument frame vetoes that parameter (the X2-defer-arg mirror).

**Determinism across emissions:** classification reads only the production package's own files — never `_test.go` — so the `-stdlib` and `-tests` emissions of production sources agree by construction (a white-box `export_test.go` func-value alias cannot un-lower what `-stdlib` lowered; unit-guarded).

Landed measured effect on the flagship: `crypto/internal/nistec/fiat` transpiles with **zero** `heap(` sites and **zero** `.of(` sites (was 158 address-taken locals and 56 field-ref argument feeds), per the design's §3.6 projection. (Guarded by the `RefLoweredParams` behavioral test — write-through, forwarding chains, the mixed kept/reverted local, the defer/go carve-out in all three observable directions, the X5 func-value exclusion — and `RefLoweredNilTiming` — the eager-panic differential in three nil spellings plus the deferred-fault half, all output-compared against `go run`. The classifier and its fixed point are unit-guarded in `refLoweringAnalysis_test.go`; the corpus-wide census instrument is `-ref-census`.)

### The lowered emission, row by row — the seven argument shapes in emitted code

The seven rows of [`DESIGN-zh-box-reduction.md`](phase4/DESIGN-zh-box-reduction.md) §3.3, each with its emitted form and the golden that pins it. Every snippet is verbatim from a committed `.cs.target`, quoted through [`EXEMPLARS-a2-ref-lowering.md`](phase4/EXEMPLARS-a2-ref-lowering.md) — which carries the before/after pair and the history for each; only the *current* form is stated here.

| # | Go argument | boxed emission | lowered emission | golden |
|:-:|:--|:--|:--|:--|
| 1 | `&e.x` — field of a deref'd parameter or receiver (a **nullable** base) | `Ꮡe.of(T.Ꮡx)` — 1 box | `ref nonnil(ref e).x` | `RefLoweredParams`, `GenericReceiverFieldAddress` |
| 2 | `&x` — an address-taken local, value parameter or named result | `Ꮡx`, the `heap()` box minted at the declaration | `ref x` — the plain local; the box and its eager `T[1]` slot are gone | `ForVariants` |
| 3 | a pointer variable/field/deref/assert `q` — it carries a box | `q` | `ref (q).DerefOrNull()` — read at CALL time, so a re-pointed pointer is never stale | `PointerParamNilWalk`, `PointerFieldArrayElementAddress` |
| 4 | `&s[i]` / `&x.f` over a **value-rooted** base (local, value param, global, slice) | `Ꮡ(s, i)` — 1 box + 1 interface temp; `Ꮡx.of(T.Ꮡf)` for the field form | `ref s[i]` / `ref x.f` — `nonnil` elided, the base provably cannot be null | `AddressOfParamWrite`, `PointerFieldArrayElementAddress` |
| 5 | `(*T2)(&v.x)` — a pointer conversion over a `[GoType]` named-**array** wrapper | `Ꮡ((Ꮡv.of(…)).Value.Value)` — 2 boxes | hoisted temp: `var ᴛ1 = v.x.Value;` … `ref ᴛ1` | `NamedArrayWrapper` |
| 6 | a non-variable pointer expression — `&T{…}`, `new(T)`, any call result | `Ꮡ(new T(…))` / carries the returned box | hoisted temp, same shape as row 5: `var ᴛ1 = new T(…);` … `ref ᴛ1` | `RefLoweredParams` |
| 7 | the literal `nil` | `default!` | `ref ((ж<T>)default!).DerefOrNull()` — binds the null ref; the callee faults at first use | `GuardedNilPointerParamDeref` |

A lowered parameter forwarded into another lowered position is `ref p` — it already is the ref. Rows 5–7 share one justification: a lowered callee can never compare, store, escape or convert the address, so a caller-side temporary is observationally identical to a distinct heap box.

**Row 1 — the parameter *is* the alias, and it survives generic instantiation** (`GenericReceiverFieldAddress`; the callee's `ж<T> Ꮡp` box and its `DerefOrNull()` preamble are gone, and the caller's 128-byte-per-evaluation field box becomes free):

```csharp
internal static void setT<T>(ref T p, T val) {
    p = val;
}

public static void Set<T>(this ж<Box<T>> Ꮡb, T val) {
    ref var b = ref Ꮡb.DerefOrNull();

    setT(ref nonnil(ref b).v, val);
}
```

**Row 2 — an address-taken local comes home from the heap** (`ForVariants`; two counted objects per unmanaged local removed, and the per-iteration boxing scaffold of a labeled loop collapses to one plain loop variable):

```csharp
nint i = 0;
while (i < 10) {
    f(ref i);
    i++;
}
internal static void f(ref nint y) {
    fmt.Print(y);
}
```

**Row 3 — the callee lowers, the call site unwraps** (`PointerFieldArrayElementAddress`; `c` comes from `.at(…)` indexing and so still carries a box — each function makes its own deal and the convention change composes across the boundary):

```csharp
internal static void bump(ref cycle c) {
    c.n++;
}
internal static void viaParam(ж<rec> Ꮡp, nint i) {
    var c = Ꮡp.at(rec.Ꮡfuture, i);
    bump(ref (c).DerefOrNull());
}
```

The same row, dereferenced per call rather than bound once, is what keeps a **reassigned** pointer honest (`PointerParamNilWalk`, whose walk loop emits `advance(ref (Ꮡp).DerefOrNull())`); note also what does *not* lower there — a pointer escaping through a **return** keeps its box identity, so `advance`'s `(ж<node>, nint)` result is unchanged.

**Row 5 — two boxes become one temp** (`NamedArrayWrapper`; the wrapper's `Value` yields an `array<T>` header whose `T[]` backing is SHARED, so element writes flow through and whole-header writes are lost in *both* emissions equally — byte-parity, not a new behavior. Type-gated by `refConvPairingSupported` to the identical-underlying-array family; a string or numeric wrapper's value is a plain copy and keeps its identity box end to end):

```csharp
scal sm = new();
var ᴛ1 = sm.s.Value;
fromBytes(ref ᴛ1, 7);
var ᴛ2 = (nonMont)((sm.s).Value);
@double(ref sm.s, ref ᴛ2);
```

**Row 7 — a lowered parameter still accepts Go's `nil`** (`GuardedNilPointerParamDeref`; the synthesized argument binds a null box and defers the fault to the first actual use inside the callee, which is Go's "a nil pointer only panics when dereferenced" timing. `RefLoweredNilTiming` pins it against `go run`):

```csharp
internal static nint digits(nint @base, ref nint invalid) {
    ...
}
nint c2 = digits(10, ref ((ж<nint>)default!).DerefOrNull());
```

**The counter-examples are guarded beside the lowered ones** (`RefLoweredParams`), so the boundary is itself under test: a parameter compared to `nil` keeps its box (its *identity* is observed); one used as a func value keeps it (a method group cannot close over a `ref`); a `defer`/`go` site keeps it and derives the ref at invoke time (`defer(ᴛ1 => bump(ref ᴛ1.DerefOrNull()), Ꮡresult, ref ᒐ);`); in-lambda call sites are uniformly boxed-fallback wrapped `.DerefOrNull()`; string/numeric wrapper reinterprets sit outside row 5's family; and a blank or unnamed pointer parameter is never a candidate.

### Pointer-typed globals and double-pointer walks (`&head`, `*pp`, `ValueSlot`)
A package-level global of **pointer type** whose address is taken — `var head *node` with `pp := &head` — is heap-boxed like any addressed global, yielding a **double box**: `ж<ж<node>> Ꮡhead`. Three rules make the classic linked-list walk (`for pp := &head; *pp != nil; pp = &(*pp).next { … *pp = n }`) faithful:

1. **One star is ONE deref.** `*pp` on a `**T` yields a `*T` — a single `.Value`/`.ValueSlot` hop, never two. (An older arm added an extra `.Value` per pointer *depth*, double-dereferencing every single-star of a double-pointer field — runtime `mheap.go`'s `specialsIter` walk failed CS0029 in both assignment directions.) A genuine `**pp` is two nested `StarExpr`s, each contributing its own hop. Likewise a *field read through an explicit single star* on a `**T` — `(*outer.ptr).Value` — keeps the base pointer-typed after one star, so normal pointer-base field handling supplies the remaining auto-deref: `(~(outer.ptr.Value)).Value`.
2. **A deref whose *result* is still reference-like reads `ValueSlot`, not `Value`.** Go's `*pp` may legally yield nil (`*pp != nil` is the loop condition); only *dereferencing* that nil panics. golib's strict `Value` accessor nil-checks the slot, so a deref (or boxed-global property) producing a pointer/slice/map/chan/func/interface value routes through `ж<T>.ValueSlot` — the **identical real slot with no nil check** — and reads *and writes* both persist: `pp.ValueSlot = n` lands in the original global storage. A deref producing a plain **value** keeps the strict `Value` (a nil `*node` deref must panic, as in Go). The boxed global's ref-property follows the same split: `internal static ref ж<node> head => ref Ꮡhead.ValueSlot;` for the pointer-typed global, `=> ref Ꮡg.Value;` for a value-typed one.
3. **`&global` on an addressed global is the identity box, never a copy.** `&allm` (where `var allm *m` is boxed) emits `Ꮡallm` — the existing box — not `Ꮡ(allm)`, which would heap-allocate a *copy* and silently disconnect writes. And `&(*pprev).alllink` (address of a field behind one explicit star) peels the star and goes through the field-box accessor: `pprev.Value.of(m.Ꮡalllink)`.

The full emitted walk:

```csharp
internal static ж<ж<node>> Ꮡhead = new(default(ж<node>));
internal static ref ж<node> head => ref Ꮡhead.ValueSlot;

for (var pp = Ꮡhead; pp.ValueSlot != nil; pp = (pp.ValueSlot).of(node.Ꮡnext)) {
    if ((~(pp.ValueSlot)).val == v) {
        pp.ValueSlot = (pp.ValueSlot).Value.next;   // *pp = (*pp).next — write lands in real storage
        ...
```

This is exactly the runtime's `allm`/`itabTable` shape (`for pprev := &allm; *pprev != nil; pprev = &(*pprev).alllink`). (Guarded by the `GlobalPointerWalk` behavioral test — ordered insertion, head/middle removal, and a method call through the pointer global, all via `**node` writes, output-compared against Go.)

### Capturing the address of a heap-boxed local in a closure
A local whose address is taken (`&m`) is heap-boxed: the converter emits `ref var m = ref heap(new T(), out var Ꮡm)`, where `Ꮡm` is the box and `m` is a `ref`-local alias of `Ꮡm.Value`. When a **function literal captures such a local and takes its address inside the closure**, the variable must be referenced through the box, not snapshot-copied. A C# `ref`-local cannot be captured by a lambda (CS8175), and the older snapshot capture (`var mʗ1 = m;`) is wrong twice over: it copies the *value* out of the box (so writes through the captured `&m` are lost), and the copy declaration is a statement that has nowhere valid to land when the literal sits in an expression position — e.g. a func literal passed as a **call argument** (`run(func(){ use(&m) })`) or a local initializer (`f := func(){ use(&m) }`).

The fix: a heap-boxed local whose address is taken inside a lambda is marked *box-ref* and the snapshot is suppressed. The box `Ꮡm` is a plain local (a capturable reference), so the C# closure captures it by reference — matching Go's capture-by-reference semantics. Inside the closure the converter then renders every form through the box:

```csharp
ref var m = ref heap(new box(), out var Ꮡm);
run(() => {
    set(Ꮡm);                       // &m  → Ꮡm
    Ꮡm.Value.y = Ꮡm.Value.x + 1;       // value use of m → Ꮡm.Value
});
// &m.field (value struct field) → Ꮡm.of(box.Ꮡfield)
```

This also covers `&m.field` (a value-struct field address inside the closure: `Ꮡm.of(box.Ꮡfield)`). The detection is scoped to the bare `&m` and value-struct `&m.field` forms (the ones with a box-ref emission form); an element address `&m[i]` keeps the existing snapshot path. The behavioral test `FuncLitArgCapture` guards the call-argument, value-use, field-address, and initializer cases.

### A capture that is WRITTEN after the capture point routes to shared storage, not a snapshot
Go closures share the ONE variable with the enclosing function. The value snapshot the converter uses for captured structs/arrays/slices/maps/chans (`var tʗ1 = t;` hoisted before the lambda, in-lambda references renamed to `tʗ1`) is therefore only *observationally* correct while **neither side writes the variable after the snapshot point**. Once anything does, the snapshot silently diverges — the program compiles and runs, with wrong values:

- a closure's writes land in a divorced copy: `bump := func() { t.total += 100 }` never affects `t` (probe-proven: Go 106, snapshot 6);
- body writes after the lambda's creation are invisible to a read-only closure: `get := func() int { return t.total }; t.total += 10` reads the stale copy (Go 15, snapshot 5);
- a **deferred** literal observes the variable's registration-time value instead of Go's final value (`defer func(){ fmt.Println(t.total) }(); t.total = 42` — Go 42, snapshot 5);
- two closures over one variable each get their own copy, so a writer and a reader stop communicating entirely.

The converter now detects **written-after-capture** per variable during analysis (`varShareFacts`, one cached scan of the enclosing declaration) and routes such captures to shared storage. Writes counted, conservatively by syntax: an assignment or `++`/`--` whose target roots at the variable's own storage (`t = …`, `t.f.g = …`, array `a[i] = …` — but *not* through a deref, an implicit pointer deref `p.f = …`, or a slice/map element, which a snapshot copy shares anyway); a pointer-receiver method call on the variable held as a value (Go's implicit `&t`); a `for t = range` clause; an explicit `&t` anywhere or an uncalled pointer-receiver method value (an **alias** — later writes through it are syntactically invisible, so it counts at any position, e.g. `p := &t; get := func(){…}; p.total = 50`); and any of these inside any func literal (the literal may run at any time). A plain body write counts only if positioned after a referencing literal or sharing a `for`/`range` loop with one (a later iteration's write follows an earlier iteration's creation).

The routing, by variable shape:

```csharp
// Heap-boxed variable (escaping struct local, aliased int, …) → by-box (boxRefVars):
ref var t = ref heap<Tally>(out var Ꮡt);
t = new Tally(5, "s");
var bump = () => {
    Ꮡt.Value.total += 100;      // value use → Ꮡt.Value: writes the ONE box the body reads
};

// Unboxed variable (value parameter; slice/map/chan local, whose copy diverges on
// reassignment) → NATIVE C# capture — no snapshot, no rename; the display class
// shares the local exactly as Go shares the variable:
internal static void probeB1(Tally t) {
    var bump = () => {
        t.total += 100;          // captures the parameter itself
    };
    bump();
    t.total++;                   // 106, matching Go
```

This applies only to genuine **closure-body** references (a func literal's body, directly or as a go/defer statement's literal callee). A go/defer statement's non-literal callee/receiver expression and its call arguments keep their statement-time evaluation — `defer fmt.Println(t.total)` still prints the registration-time value, which IS Go's argument semantics. Read-only-after-capture variables keep the snapshot (observationally identical, zero churn — the vast majority of stdlib captures). A **loop-statement-defined** variable (for-init or range clause) also keeps it: its per-lambda snapshot approximates Go 1.22's per-iteration variable, which shared routing would break. A literal's own parameters/results are not captures and are excluded. (The remaining known gap, deliberately out of scope here: a `for`-init variable captured by closures diverges from Go 1.22 per-iteration semantics — the C# `for` control variable is shared across iterations — tracked as its own defect.)

The native route makes the emitted C# read exactly like the Go for the parameter case; the by-box route reuses the box-ref machinery above (including `ValueSlot` for inherently-heap locals: a captured slice that the closure reassigns emits `Ꮡs.ValueSlot = append(Ꮡs.ValueSlot, …)` against a materialized `heap<slice<T>>` box). (Guarded by the `ClosureWriteVisibility` behavioral test — 19 probes: boxed/plain × local/param × closure-writes/body-writes-after × plain/defer/go/IIFE/loop-created contexts, plus slice/map reassignment, alias writes, two-closure sharing, and the read-only/defer-argument/range controls that must KEEP snapshot semantics.)

**A NAMED RESULT routed to shared storage declares its box too.** The `defer func(){ hook(written, err) }()` idiom is exactly the written-after-capture shape above with the captured variable being a *named result* — Go's deferred closure must observe the FINAL named-result values. When the escape analysis marks such a result (an interface-typed result is blanket-marked the first time it is reused on a mixed `v, err := …` define; a value-type one when `&x` is taken), the render sites duly go through the box (`Ꮡerr.ValueSlot` inside the deferred literal) — but the named-result declaration prologue emitted only the plain `error err = default!;`, leaving `Ꮡerr` undeclared (CS0103 — internal/poll `SendFile`'s deferred `TestHookDidSendFile`, the single error skip-cascading ~80 os-dependent packages). A box-backed named result (`identHasHeapBox`, the same gate plain locals use) now declares the box, in three shapes:

- **No defer frame** (plain function, or a closure writing the result): the full escaping-local form at the declaration site — `ref var err = ref heap<error>(out var Ꮡerr);` — body and bare returns keep reading the plain alias, nested closures read/write `Ꮡerr.ValueSlot`. A value-type result with `&x` gets `ref var x = ref heap(new nint(), out var Ꮡx);`, making the write through `&x` visible to the bare return (previously that shape was also CS0103).
- **namedReturnDeferMode** (a function whose results are declared before the frame's `try`): the decls sit OUTSIDE the `try`, and the deferred closures that read them are lambdas, which cannot capture a `ref` local (CS8175) — so the outside line creates only the box (`heap<error>(out var Ꮡerr);`), the `try` re-derives the value alias inside (`ref var err = ref Ꮡerr.ValueSlot;`, exactly like a deref'd pointer parameter's `ref var fd = ref Ꮡfd.Value;`), and the post-`finally` return reads through the box: `ᒐdone: return (written, Ꮡerr.ValueSlot);` (internal/poll `sendfile_windows.cs`).
- **Func-literal sibling** (a literal with named results + defer + post-capture writes): same split, except the literal's body is itself a lambda conversion, so every in-`try` use already renders through the box — including the explicit-return rewrite's assignment targets (`(var v, Ꮡe.ValueSlot) = pair(n);`) — and the literal's trailing `return (w, Ꮡe.ValueSlot);` reads the box.

The box-read accessor follows the box-ref rule above: `.ValueSlot` for an inherently-heap result (reading the held reference is not a dereference), `.Value` for a value-type box. Results NOT escape-marked are untouched — `written` in the same defer stays a plain local captured natively by the C# closure, which already observes the final value. (Guarded by the `NamedResultDeferCapture` behavioral test — value + error named results logged by a deferred closure with post-capture writes and bare returns, the `&x` value-result, the func-literal sibling, and a non-defer closure write; output-compared vs Go, proving the deferred observation of FINAL values. Stdlib footprint: 12 functions across 10 files — internal/poll, net/http, go/parser, crypto/tls, internal/fuzz, debug/buildinfo, both go importers, net/textproto.)

**A PARAMETER routed to shared storage declares its box too** — the third position of the same family (plain locals, named results, parameters). A parameter can be escape-marked without any capture-mode method call: a body-top-level mixed `:=` REDECLARES the parameter object (the spec's redeclaration rule includes the parameter lists when the block is the function body), so the define walker escape-analyzes it — and an interface-typed one is blanket-marked. When such a parameter is also captured by a closure and written after the capture point, the routing above sends it by-box (`Ꮡctx.ValueSlot` inside the lambda) — but the parameter prologue only boxed for the capture-mode (direct-ж) trigger, leaving the box undeclared (CS0103): database/sql `beginDC`'s `ctx` (redeclared by `ctx, cancel := context.WithCancel(ctx)` after `withLock`'s closure captured it) and go/types `nify`'s `x, y` (swapped by `x, y = y, x` and redeclared by `xorig, x := x, Unalias(x)` after the trace defer captured them). `paramNeedsHeapBox` (and its func-literal analogue `funcLitHeapBoxParamIdents`) now also fires for a box-ref-routed parameter, emitting the exact capture-mode form — the signature takes the incoming value as `ctxʗp` and the preamble declares `ref var ctx = ref heap(ctxʗp, out var Ꮡctx);` (inside the frame's `try` when the function has a frame, where the box is an ordinary capturable local). Body statements keep reading/writing the plain ref alias — the redeclare emits `(ctx, var cancel) = …` against it — so both sides hit the ONE box, and a deferred observer sees Go's FINAL values. The check rides the declaring-ident lookups, so a box-ref'd value RECEIVER (never `ʗp`-renamed by the signature paths) can never take the param form. (Guarded by the `WrittenCaptureParam` behavioral test — the beginDC redeclare shape, the nify deferred-observer shape (named result + defer frame), a closure-write read back by the body, the func-literal sibling, and an inherently-heap slice param; all output-compared vs Go. Stdlib footprint: exactly `database/sql/sql.cs` + `go/types/unify.cs`.)

### A write that ENCLOSES the literal counts as written-after-capture — the self-recursive closure

The write scan above compares *positions*: a body write counts when it sits after a referencing literal, or shares a loop with one. Go's standard recursive-closure idiom defeats a pure position test, because the write **starts before** the literal it contains:

```go
var check func(uint32, []bool) bool
check = func(pc uint32, m []bool) (ok bool) {
	…
	ok = check(inst.Out, m) && check(inst.Arg, m)   // recurses through the variable
	…
}
```

The assignment statement's position is that of `check` on its left, which precedes the literal on its right — yet the RHS is evaluated *first*, so the store to `check` unambiguously happens after the literal exists. Scored as read-only-after-capture, the capture took the snapshot path (`var checkʗ1 = check;` hoisted **above** the assignment) and every recursive call invoked the still-**null** delegate: a `NullReferenceException` on the first recursion. In regexp's `makeOnePass` that is the entire ambiguity check, so `^.$` — and most of the package — became uncompilable. The scan now also counts a write whose syntactic extent *contains* a referencing literal (`w.pos < lit.pos < w.end`), which routes the capture to shared storage; `check` escapes, so it takes the by-box form and the recursion resolves against the box the assignment fills:

```csharp
ref var check = ref heap<Func<uint32, slice<bool>, bool>>(out var Ꮡcheck);
check = (uint32 pc, slice<bool> mΔ1) => {
    …
    ok = Ꮡcheck.ValueSlot((~inst).Out, mΔ1) && Ꮡcheck.ValueSlot((~inst).Arg, mΔ1);
    …
};
```

The same edge covers **mutually** recursive closures (`even`/`odd`, each literal enclosed by the write to its own name while reading the other), and it generalizes beyond closures: any write that evaluates a referencing literal as part of itself — `t.mutate(func(){ use(t) })` — now counts. (Guarded by the `ClosureWriteVisibility` probes Q1/Q2 — a self-recursive sum and a mutually recursive parity pair; the pre-fix converter compiles both and nil-derefs at the first recursive call.)

### A nested closure's capture snapshot reads the enclosing closure's snapshot
When a heap-boxed **ref-local is used by VALUE** (its address is not taken) and captured by NESTED closures, it is not box-ref'd — it is snapshot-copied: the converter declares `var mʗ1 = m;` before the closure and the closure uses `mʗ1`, so the uncapturable `ref`-local `m` is never referenced inside the lambda. The snapshot chain must be threaded through each level. A capture generated for an **inner** closure that lands inside an **outer** closure's body must read the outer closure's snapshot, not the enclosing method's ref-local — the shape testing/fuzz.go's `run` closure has, capturing `fn := reflect.ValueOf(ff)` (a heap-boxed `reflect.Value`) and spawning `go tRunner(t, func(t){ … fn.Call(args) })` from inside itself, where the method-level `fn` is a ref-local uncapturable inside a closure (CS8175). The guard's own shape emits it:

```csharp
ref var p = ref heap<payload>(out var Ꮡp);
p = new payload(vals: new nint[]{1, 2, 3, 4}.slice());
var @out = new channel<nint>(1);
var outʗ1 = @out;
var pʗ1 = p;                   // outer's snapshot (before the outer closure)
void outer() {
    var outʗ2 = outʗ1;
    var pʗ2 = pʗ1;             // the goroutine's snapshot reads outer's pʗ1, NOT p
    goǃ(() => {
        outʗ2.ᐸꟷ(pʗ2.sum());
    });
}
```

`generateCaptureDeclarations` finds the RHS by walking the conversion stack outward past pass-through levels (a `go`/`defer` statement's own `enterLambdaConversion`, which carries an empty rename map) to the first enclosing lambda that renamed the variable. It skips the capture's OWN owner state — `pendingCaptures` is shared across a function's lambdas, so an outer lambda's snapshot can be generated while converting an inner func-literal argument (`go dnsWaitGroupDone(ch, func(){})`, net/lookup.go), leaving the owner's state on the stack with a rename equal to the name being declared; adopting it would emit a self-reference `var fʗ1 = fʗ1;` (CS0841). Byte-identical corpus-wide except where a nested closure re-captures a heap-boxed local. Guarded by `FuncLitArgCapture` (a heap-boxed struct re-captured in an inner goroutine — CS8175 without the fix — and the `go f(x, func(){})` self-reference shape) and by `DeferValueFieldPtrReceiver` (a defer inside a lambda).

A **pointer (or other inherently-heap) local** captured by a closure that takes its address needs the box too, but reaches it by a different route. A local of an *inherently heap-allocated* type — a pointer, slice, map, channel, interface, or func — is already a reference, so it normally gets **no** heap box (the `convertToHeapTypeDecl` path returns nothing for such types). But when one is captured by a closure that takes its address (`mToFlush := &node{…}; run(func(){ prev := &mToFlush; … *prev = mToFlush.next })`), the closure needs a *shared* box so writes through `&mToFlush` inside it reach the outer function's storage. The converter detects this as the same *box-ref* mark used above (an inherently-heap local whose address is taken inside a lambda), and for a box-ref local it now emits the heap box even though the type is inherently heap — `ref var mToFlush = ref heap<ж<node>>(out var ᏑmToFlush)` — so the box `ᏑmToFlush` (a `ж<ж<node>>`, i.e. a `**node`) exists for the closure to reference. Without it the closure emitted `ᏑmToFlush` for `&mToFlush` against a never-declared box (CS0103); a same-function `&ptr` with **no** closure still takes the `Ꮡ(ptr)` copy form (a copy is fine there — no shared storage is needed), so that case is unchanged.

Reading such a box needs care, because for a box-of-pointer the held value can legitimately be nil while the box itself is a real allocation. `Ꮡm` here is a `ж<ж<node>>` (a `**node`), so `Ꮡm.Value` reads the *held pointer value* — not a dereference of `Ꮡm` — and in Go reading `*(&p)` when `p` is a nil `*T`/slice/map yields the nil value, with no dereference and no panic. The strict `ж<T>.Value` getter (which panics on a null stored value by design, so a genuine `*p` on a nil pointer still throws) would wrongly panic on that read. So the converter emits the golib `ж<T>.ValueSlot` accessor for these box-of-pointer reads — identical to `.Value` but without the nil-pointer-dereference check, returning the *real* slot so reads and writes both persist (and unlike the retired `DerefOrNil`, which yielded a throwaway slot for a genuinely-nil box). `ValueSlot` is selected here for a box-ref **local** of inherently-heap type; a deref'd pointer *parameter* reaches the same slot through `DerefOrNull()`, whose non-nil path IS `ValueSlot` — see *The THREE deref accessors of `ж<T>`*. The `heap(out …)` / `heap(target, out …)` helpers likewise return `ref pointer.ValueSlot`: a freshly allocated box is structurally non-nil, so the getter's nil check there is always spurious (identical to `.Value` for a value-type box; it just avoids a spurious panic when establishing the `ref var mToFlush = ref heap<ж<node>>(out var ᏑmToFlush)` alias). A genuine dereference of the held pointer (the second `.Value` in `ᏑmToFlush.ValueSlot.Value.v`) stays strict and still panics on nil — preserving Go's "panic ⇒ panic" semantics, and complementing the deliberate strict-`.Value` design at every genuine USE site. (Guarded by the `ClosureCapturedPointerAddress` behavioral test — a closure that takes the address of a captured pointer local, walks a linked list by reassigning *through* that address and mutating each node, with the outer function observing both the reassignment-to-nil and the persisted mutations, proving the box is shared rather than copied. Mirrors runtime's `trace.go` `mToFlush := allm; systemstack(func(){ prev := &mToFlush; … mToFlush = mToFlush.next })`, ~4 CS0103.)

A pointer-receiver method called **through a FIELD of such a boxed pointer local**, inside the closure, field-refs through the **held pointer**, not the box. The receiver of `c.flushGen.Store(…)` (runtime `mcache.go`'s `allocmcache`, inside `systemstack`) is taken via the &-machinery, and inside a lambda the box-ref address form substitutes the capturable box for the uncapturable ref-local alias. For a *value*-struct local (box `ж<T>`) and a deref'd pointer *parameter* (box `ж<T>` — the Go pointer itself) the bare box is the correct `.of()` receiver — but a boxed pointer LOCAL's box is `ж<ж<T>>`, one level above the `ж<T>` the field accessor projects from, and feeding it to `.of` fails inference (CS0411 — the one error that skip-cascaded ~237 packages behind `runtime`). Such a base declines the bare-box form and falls through to the pointer-variable field arm, whose ident render reads the box the same way every other in-lambda value use does: `Ꮡc.ValueSlot.of(mcache.ᏑflushGen).Store(…)` — `.ValueSlot` because reading the held pointer out of the box must not nil-check (the dereference happens in `.of`, preserving panic semantics), and because that slot IS what the enclosing `ref var c = ref heap<ж<mcache>>(out var Ꮡc)` alias reads. When such a local is **named after its own type** (`gauge := newGauge()`), the accessor's owning-type name additionally qualifies with the package class (`Ꮡgauge.ValueSlot.of(main_package.gauge.Ꮡv)`): the enclosing `ж<gauge>`-declared local stays visible inside the lambda, so the bare type name binds the uncapturable ref-local (CS8175) with no identical-simple-name fallback — the declared type differs from the type name. (Guarded by the `ClosurePtrLocalFieldMethod` behavioral test — the `allocmcache` shape: a pointer local written inside a closure and immediately method-called through a value field, read back after the closure, plus the named-after-type variant; output-compared vs Go, proving the write-through and the field-method call both bind the one shared box.)

A **deref'd pointer parameter or pointer receiver** captured by a closure is box-ref'd the same way, even when only its *value* is used inside the closure (not its address). Such a parameter is emitted as the box `ж<T> Ꮡp` with `ref var p = ref Ꮡp.DerefOrNull()`, and the `ref`-local alias cannot be captured (CS8175). Inside the closure a value use becomes `Ꮡp.Value.field` and an address use `Ꮡp`, so the closure captures the box by reference — matching Go capturing the pointer. (Guarded by the behavioral test `PointerParamCapturedInClosure`; the runtime captures `*maptype` / `*m` parameters this way pervasively.)

A pointer **receiver** captured by a closure needs an extra step the parameter case does not: the box `Ꮡp` only exists if the method is emitted **direct-ж** (the box passed *as* the receiver, `this ж<T> Ꮡp`). A normal pointer-receiver method is `[GoRecv] this ref T p` (a value-ref receiver, with the `ж<T>` companion generated separately), which has no box for the closure to reference. So "the receiver is referenced inside a function literal" is a **direct-ж trigger** — a fourth one alongside taking a field's address (`&p.field`), returning the receiver (`return p`), and using the receiver as a bare pointer value (`p.next = p`, `p != q`). Mirrors runtime's `func (p *_panic) nextFrame() { systemstack(func(){ … p.lr … }) }`. A closure parameter that shadows the receiver name resolves to a distinct object, so it does not falsely trigger the promotion. (Guarded by the `ReceiverCapturedInClosure` behavioral test — receiver captured by an immediately-invoked closure that reads/writes through it, by one that takes a field's address, and by one that is *returned* so the box must outlive the call.)

Once a method is direct-ж, its receiver is the box `Ꮡc`, but the deref'd value alias `ref var c = ref Ꮡc.DerefOrNull()` is what most uses see. When such a receiver is passed **whole** as a pointer argument — `stackcache_clear(c)` in `func (c *mcache) prepareForSweep()` — the argument must be the box `Ꮡc`, not the value alias `c` (a value cannot bind a `ж<mcache>` parameter → CS1503). A deref-aliased pointer *parameter* is already handled (it is an `identIsParameter`), but a direct-ж *receiver* is not a parameter, so the call-argument conversion recognizes it explicitly and emits the box. (Guarded by the `DirectBoxReceiverPassedWhole` behavioral test.)

The receiver placed whole into a **composite-literal element** whose field is a pointer — `func (f *_func) funcInfo() funcInfo { …; return funcInfo{f, mod} }` (runtime `symtab.go`; `funcInfo`'s first field is the embedded `*_func`) — needs the same box, and is itself a **direct-ж promotion trigger** (`bodyUsesReceiverAsPointerValue`'s composite arm): a boxless `[GoRecv] ref` receiver has no `Ꮡf` to place in the field (CS1503). Once promoted, the composite renders the box through the existing pointer-field element machinery: `new ΔfuncInfo(Ꮡf, mod)`. Both positional and keyed elements trigger, gated on the **field's declared type being a Go pointer** (resolved positionally or by key from the composite's struct type — the element expression's own type is always `*T` for a pointer receiver): a receiver placed into an *interface*-typed field also typechecks in Go, but that emission compiles today, and promoting for it would re-route every such method stdlib-wide (the field gate trims the first-cut 73-file audit to 68 — the shape is genuinely pervasive: go/types' Checker methods, net/textproto's dotReader{r: r}, zstd readers — every audited site the same signature+box re-routing) — its pointer-identity semantics are logged as a separate question. (Guarded by the `DirectBoxReceiverPassedWhole` extension — positional + keyed composites, identity verified by writing through the wrapped pointer and reading the original.)

The same composite arm also fires when the receiver is stored **as an element of a SLICE or ARRAY literal whose element type is a pointer** — `func (s *UserTaskSummary) Descendents() []*UserTaskSummary { descendents := []*UserTaskSummary{s}; … }` (internal/trace `summary.go`). Without promotion the boxless `[GoRecv] ref` receiver renders the value alias `s` into a `ж<T>[]` slot (CS0029); once promoted direct-ж, the element renders the box: `new ж<UserTaskSummary>[]{Ꮡs}.slice()` (and `[2]*T{s, other}` → `new ж<T>[]{Ꮡs, Ꮡother}.array()`). Gated on the **slice/array element type being a pointer** (the `*types.Slice`/`*types.Array` arms of `bodyUsesReceiverAsPointerValue`), mirroring the struct-field pointer gate. (Guarded by the `ReceiverPointerValue` extension — the receiver stored into a `[]*ring` and a `[2]*ring` literal, `chain[0]` identity verified by mutating through the stored pointer and reading back through the receiver.)

The same pointer-element boxing must also fire for an **ELIDED (type-inferred) nested composite** — the inner `{c}` of `[][]*Certificate{{c}}` (crypto/x509 `Verify`). The inner literal has no `Type` node; its inferred element type is `*Certificate`, and its sole element `c` is the deref-aliased `*Certificate` receiver. The typed composite path boxes a bare pointer-typed ident element (`argTypeIsPtr`), but the untyped-elided slice/array path rendered its elements with a **nil** context, so that treatment never ran and `c` emitted the value alias into a `ж<Certificate>[]` array (CS0029). The elided path now supplies a context that boxes a bare pointer-typed ident when the element type is a pointer — `new ж<Certificate>[]{Ꮡc}.slice()` — returning nil (unchanged nil-context rendering) when the element type is not a pointer or no element is a bare pointer ident, so non-pointer elided literals stay byte-identical. (Guarded by the `ElidedNestedPtrComposite` behavioral test — `[][]*Node{{n}}` where `n` is a pointer receiver.)

A **MAP** composite literal whose value or key type is a pointer boxes its element the same way — but through `convKeyValueExpr` (the `[key] = value` form), not the slice/array element loop above. `map[K]*T{k: c}` where `c` is a deref'd pointer parameter renders the value alias `c` into a `ж<T>` map slot (CS0029); the map-source branch of `convKeyValueExpr` now sets the `isPointer` ident context for the VALUE when the map's declared element type is a pointer, so a bare-ident pointer value emits its box `Ꮡc` — `new map<@string, ж<node>>{["a"u8] = Ꮡa}`. A pointer-KEY map (`map[*T]V{c: 1}`) boxes the key the same way (`new map<ж<node>, nint>{[Ꮡa] = 1}` — the `ж<T>` dictionary key matches by box identity). Gated on the map's declared **element/key type being a pointer** (not an interface — an interface-valued map still routes through the interface conversion) *and* the element expr's own type being a pointer, so a value already rendered as a box (`&x`, a pointer local) is unaffected. (Guarded by the `MapPointerElementLiteral` behavioral test — a pointer-value map and a pointer-key map built from `*node` parameters, aliasing verified by mutating through a stored value and looking up by pointer-key identity.)

**Reassigning a pointer parameter to a new pointer.** A `*T` parameter that walks memory by reassignment — `bits = addb(bits, n)` (a `*byte` step in the runtime's bitmap scanners) or `p = p.next` (a list walk) — cannot write through its value alias: `ref var bits = ref Ꮡbits.Value` makes `bits` the pointed-to *value*, and a pointer RHS (`ж<byte>`) does not fit it (CS0266/CS0029). The reassignment instead repoints the **box** and re-aliases the value var — `Ꮡbits = addb(Ꮡbits, n); bits = ref Ꮡbits.Value;` — reusing the same box-reassignment path that handles a direct-ж receiver's `r = r.prev` (the RHS already emits the box form). (Guarded by the `PointerParamWalk` behavioral test, a circular-list walk that reassigns the parameter and reads the pointed-to value each step.) Reassigning a *pointer local* (not a parameter) is unaffected — a local already holds the box.

**Reassigning a captured pointer parameter inside a closure.** The repoint-and-re-alias above (`Ꮡp = …; p = ref Ꮡp.Value;`) rebinds a `ref`-local. Inside a CLOSURE that captured the parameter that is illegal: the re-aliased value var is an ENCLOSING `ref`-local, and C# forbids referencing an outer `ref` local inside a lambda (CS8175 — crypto/x509 `buildChains`'s `considerCandidate` closure does `if sigChecks == nil { sigChecks = new(int) }` on the captured `*int` parameter). The box reassignment `Ꮡp = …` is legal (it writes the captured box field, hoisted to a closure field), so only the ref-local refresh is dropped inside a lambda:
```csharp
if (ᏑsigChecks == nil) {
    ᏑsigChecks = @new<nint>();          // was: … ; sigChecks = ref ᏑsigChecks.DerefOrNull();  (CS8175)
}
ᏑsigChecks.Value++;
```
Every in-lambda and post-lambda dereference of a repointed captured pointer routes through the box `Ꮡp.Value`, so the now-stale value alias is never read — an accepted modeling gap (like the nil-terminated walk's), not a miscompile. The suppression is sound because no LEGITIMATE re-alias ever occurs inside a lambda: a lambda's OWN pointer parameter is passed as the box `ж<T>` (never deref-aliased), and a heap-boxed value local is written THROUGH its box (`Ꮡb.Value = …`, never box-repointed). Guarded by `ClosureReassignsPtrParam` (a closure that reassigns a captured `*int` parameter; a non-nil runtime argument keeps the reassignment branch unreached so output stays deterministic).

The same repoint-and-re-alias applies when the parameter is reassigned **from a tuple** — `(left, x, idx) = binarySearchTree(x, idx, n/2)` (runtime `mgcstack.go`) or `pp, _ = pidleget(0)` (`proc.go`). The box-reassignment triggers matched the RHS **element-wise**, so a tuple *deconstruction* (one call RHS, several LHS) never fired them — the ж<T> tuple component was assigned into the deref'd value alias (CS0029) — and element 0's raw expression type is the whole `*types.Tuple` (never a pointer), so even a first-position pointer element missed. The per-element RHS type now comes from the call's result tuple, and the emitted form is the single-assign form verbatim: `(left, Ꮡx, idx) = binarySearchTree(Ꮡx, idx, n / 2); x = ref Ꮡx.DerefOrNull();` — the same nil-deferring re-alias every repoint takes (`(Ꮡpp, _) = pidleget(0); pp = ref Ꮡpp.DerefOrNull();`). The triggers are gated to a **reassigned** element: a `:=`-declared pointer element binds the tuple's ж<T> component into a fresh pointer local — which *is* the box — directly, and an inner `:=` local shadowing a parameter's name must not repoint the parameter's box (crypto/x509's `c, _, err := …cert(i)`). (Guarded by the `PointerParamNilWalk` extension — a nil-compared tuple-reassign walk plus a reassign-then-mutate-through probe, values vs Go.)

**Assigning `nil` to the parameter itself is a box repoint, not a write to the pointee.** Both triggers above gate on the RHS being *pointer-typed*, and the untyped `nil` literal has no type of its own — so `p = nil` missed them, rendered against the deref'd **value** alias, and emitted `p = default!`, which **zeroes the pointed-to struct** while leaving the box `Ꮡp` non-nil. The caller's `!= nil` then still passed and it walked a wiped-out object. This is regexp's `makeOnePass`, whose `p = nil` (the "not one-pass after all" bail-out) handed `compileOnePass` an `onePassProg` with an **emptied `Inst` slice** instead of a nil pointer — an index-out-of-range in `cleanupOnePass` on every pattern the one-pass analysis rejected, which is most of them. A nil RHS is now treated as pointer-valued whenever the corresponding target is pointer-typed, so it takes the ordinary repoint-and-re-alias form (nil-DEFERRING, as every repoint is):

```go
// regexp/onepass.go — makeOnePass
if !check(pc, m) { p = nil; break }
…
if p != nil { for i := range p.Inst { p.Inst[i].Rune = onePassRunes[i] } }
```
```csharp
if (!check(pc, m)) {
    Ꮡp = default!; p = ref Ꮡp.DerefOrNull();  // the POINTER goes nil; the pointee is untouched
    break;
}
…
if (Ꮡp != nil) { foreach (var (i, _) in p.Inst) { p.Inst[i].Rune = onePassRunes[i]; } }
```

A pointer **local** assigned nil is unaffected — a local already *is* the box, so `p = default!` is correct there. (Guarded by the `PointerParamNilWalk` extension `dropIfShort` — nils the parameter, returns it, and the caller then proves the original node's value survived; the pre-fix converter compiles it and reports a non-nil result with a zeroed pointee.)

**Nil-terminated walk.** A pointer-parameter walk that stops at a nil terminator — `func sumList(p *node) int { for p != nil { total += p.val; p = p.next } }` — needs two extra pieces, *modeled together*:

1. **Compare the box, not the value alias.** The loop guard `p != nil` must emit `Ꮡp != nil` (the box). Each binary operand's pointer context is otherwise taken from the *other* operand's pointer-ness, and `nil` is not a pointer type — so the param would convert in value form (`p != nil`, comparing a `node` struct value, the wrong thing). The converter forces the box form for a deref'd pointer *parameter* in a `==`/`!=` comparison. This is safe only for a parameter: a pointer *local* is already the box, and forcing it would emit a non-existent `Ꮡlocal`.
2. **Nil-deferring re-alias.** On the final step `p.next` is nil, so `Ꮡp = p.next` repoints the box to nil; re-aliasing through the plain `Ꮡp.Value` getter would then throw a nil-pointer dereference before the guard is re-checked. The deref/re-alias instead routes through the golib `ж<T>` extension `Ꮡp.DerefOrNull()`, which binds `Unsafe.NullRef<T>` when the box is nil — legal to HOLD, faulting only on USE — rather than throwing at the bind. The entry alias uses it too, so an empty-list call (`sumList(nil)`) binds without faulting at entry.

```csharp
internal static nint sumList(ж<node> Ꮡp) {
    ref var p = ref Ꮡp.DerefOrNull();
    nint total = 0;
    while (Ꮡp != nil) {
        total += p.val;
        Ꮡp = p.next; p = ref Ꮡp.DerefOrNull();
    }
    return total;
}
```

`DerefOrNull()` is **not** a substitute for a genuine dereference: reading or writing `*p` on a nil pointer (`~Ꮡp` / `Ꮡp.Value`) still panics, preserving Go semantics — and so does a read THROUGH the bound null ref, which is the whole point. Neither piece needs a predicate any more: piece 1 (the box-form comparison) is a property of the expression, and piece 2 is what EVERY pointer entry alias and repoint now emits, because a repoint is not a dereference in Go and a nil argument is not an error in Go (see *A pointer PARAMETER is nil-deferring for exactly the reason a receiver is*). Historically both were gated on the body nil-COMPARING the parameter, which covered the reassigned walk above and a nil-testing body invoked with a literal-nil argument (`defer closeIt(nil, 3)` → `p == nil`) — at the cost of a shared `default(T)` slot that let an *unguarded* deref of an actually-nil argument read a silent zero where Go panics. The unconditional accessor keeps the walk working and drops the trade. (Guarded by the `PointerParamNilWalk` behavioral test — a nil-terminated sum, a mutate-through-the-parameter pass, and an empty-list call — plus `DeferTypelessReturns`' deferred nil-argument call. `PointerParamWalk` covers the never-nil circular walk.)

A **package-level global** referenced inside a closure is *not* captured at all — it is a C# static, accessed live. A value snapshot (`var gʗ1 = g`) would copy the struct (so `&gʗ1` has no box → CS0103, and writes through the global from inside the closure would be lost) and is semantically wrong, since Go reads/writes the live global. For an address-taken (heap-boxed) global the closure references the static box `Ꮡg` directly — a method call routes as `Ꮡg.method()` and a field address as `Ꮡg.of(T.Ꮡfield)`. (Guarded by `GlobalCapturedInClosure`; the runtime does this in every `systemstack(func(){ … mheap_ … })`.)

### A func literal that is only ever CALLED emits as a C# LOCAL FUNCTION
A C# lambda that captures anything allocates **two** heap objects every time the lambda expression is evaluated: a display class holding the captured variables, and a delegate bound to it. That is charged per call of the *enclosing* function, whether or not the closure is ever invoked — 88 bytes for the two-word case, measured. Go allocates neither when its escape analysis proves the closure does not outlive the frame, which is why `time`'s `TestUnmarshalTextAllocations` asserts `want 0 allocs`, and why `parseRFC3339`'s `parseUint := func(…)` was 88 of that row's 216.

A `name := func(…){…}` whose variable is **only ever the callee of a call** is therefore emitted as a C# *local function* instead:
```csharp
//  Go:   ok := true
//        parseUint := func(s bytes, min, max int) (x int) { … ok = false … return x }
var ok = true;
nint /*x*/ parseUint(bytes sΔ1, nint minΔ1, nint max) {
    nint x = default!;
    …
    ok = false;          // the SAME `ok` — both sites are rewritten to one struct-closure field
    …
    return x;
}
nint year = parseUint(((bytes)(s[0..4])), 0, 9999);
```
Roslyn compiles a local function that is never converted to a delegate with a **by-ref struct closure**: the captured variables move into a struct that lives in the enclosing frame and is passed as a hidden `ref` parameter. There is still exactly one storage location per captured variable — the enclosing method's own uses are rewritten to the same field — so sharing, write-visibility and the capture-snapshot machinery are all unchanged. Only the heap objects are gone. The result type is rendered by the same helper `visitFuncDecl` uses, so a named Go result keeps its `/*x*/` comment and a local function reads exactly like a declared one; a single-return literal keeps the expression-bodied collapse (`byte num2(slice<byte> bΔ1) => …;`).

**The "only ever called" proof is what keeps that compilation available**, not a convenience: converting a local function to a delegate anywhere makes Roslyn fall back to a heap display class, and a local function has no value form to give a store, a return, an argument or a comparison in the first place. Every reference other than the declaring occurrence must be a call callee — which also subsumes reassignment (`f = …` is a non-call use of `f`) and address-taking, so the emitted name can never be required as a first-class value. Three further gates: the statement must be a `:=` **define** with one LHS ident and one RHS literal (a mixed `f, err := …` re-use records the name in `Uses`, not `Defs`, and binds no fresh object); it must be in statement position, since a local function is a declaration and cannot sit in a `for`/`if`/`switch` init clause; and the enclosing function declaration must be known (a literal inside a package-level `var` initializer is left alone).

A literal that **defers or recovers** is no bar: its frame is an ordinary local of the local function, declared in the local function's own body like any other, so the whole shape stays allocation-free —

```csharp
nint /*r*/ guard(nint n) {
    nint r = default!;
    GoFrame ᒐ = default;
    try {
        defer(() => {
            {
                var e = recover(); if (e != default!) {
                    r = -1;
                }
            }
        }, ref ᒐ);
        if (n < 0) {
            throw panic("negative");
        }
        r = n * 2; goto ᒐdone;
    }
    catch (Exception ᒐex) when (GoFrame.IsPanic(ᒐex, out PanicException? ᒐp)) { GoFrame.Capture(ᒐp); }
    finally { ᒐ.Run(); }
    ᒐdone: return r;
}
```

Go's two-step recursion idiom (`var f func(int) int; f = func(int) int {…}`) is an ASSIGN, not a DEFINE, so it is not this shape at all and keeps the lambda — correctly, since the recursive reference reads `f` as a value. (Guarded by the `LocalFunctionEmission` behavioral test: the `parseUint` shape with a named result and a mutated capture, the expression-bodied collapse, a struct-and-array capture mutated through the local function, two nested levels, and the deferring/recovering literal above — plus four negative controls, one per disqualifying reason: value use, reassignment, the recursion two-step, and argument position. The golden pins the emitted form; the stdout comparison against `go run` pins the capture semantics.)

### A variable DECLARED INSIDE a closure is not captured BY it
The escape analysis heap-boxes a local when something *outside* its frame can reach its storage; a closure is one such route, because the emitted C# serves the shared variable through a `ж<T>` box. The closure arm of that analysis matched on any mention of the object lexically inside a function literal's body — and for a variable declared *there*, that mention is its own declaration. So a literal's own local was treated as if the literal closed over it:
```csharp
//  Go:   testing.AllocsPerRun(100, func() { var t Time; t.UnmarshalText(in) })
Δtesting.AllocsPerRun(100, () => {
    ref var tΔ1 = ref heap(new Δtime.Time(), out var ᏑtΔ1);   // ← 128 B, and ᏑtΔ1 is never used
    tΔ1.UnmarshalText(inʗ1);
});
```
The box `ᏑtΔ1` is **never referenced anywhere in the emitted body** — `UnmarshalText` is a `this ref Time` extension, which binds the variable directly — while the identical two statements written outside a closure emitted a plain `Δtime.Time tΔ1 = default!;`. The arm now skips an object whose declaration position lies inside the literal, and the emission is the plain local. That was the other 128 of `time`'s 216.

The narrowing direction of an escape rule is the dangerous one — an under-box drops writes silently — so the proof is stated rather than assumed. Go scoping puts a literal's own local out of reach of every other frame, so there is nothing for a shared box to make visible; and every route by which such a local can *still* genuinely escape is decided by an arm that walks the **whole enclosing function body**, literal bodies included: `&x` / `&x.f` / `&x[i]` (the address-of arm), a pointer argument (the call arm), a `go`/`defer` use (their own arms), a capture-mode method call, and a pointer-receiver **method value** — Go's `(&x).M` written without the `&`. None of them is lost. The skip also keeps descending rather than stopping, so a literal **nested** inside the skipped one — which does close over the variable — still gets its own turn through the arm and still marks the escape.

(Guarded by the `ClosureLocalNoHeapBox` behavioral test. Five of its eight probes are the boxes that must SURVIVE, one per escape route, and each writes through the escaping alias and reads the value back so an over-narrowed rule prints a wrong number rather than merely emitting a different shape; the two positive probes are the pointer-receiver-method and copy-only shapes that now emit plain locals. Its N3 probe is the nesting case, and it is also the interaction test with the local-function rule above: the nested literal is emitted as a local function and captures the surviving box.)

### Capture-mode methods called through a value field of the receiver
A pointer-receiver method that takes the address of one of its own fields (`func (c *Counter) Add(d int32) int32 { return bump(&c.n, d) }`) is *capture-mode*: it is emitted with the heap box **as** its receiver (`this ж<Counter> Ꮡc`) so `&c.n` can field-reference the real storage as `Ꮡc.of(Counter.Ꮡn)`. When another struct embeds such a type as a **value field** and drives it through that field — `func (f *Flag) Incr() int32 { return f.c.Add(1) }` — the call needs a `ж<Counter>` aliasing the real `f.c`. The enclosing method is therefore itself promoted to capture-mode (direct-ж), and `f.c.Add(1)` is emitted as `(&f.c).Add(1)`:
```csharp
public static int32 Incr(this ж<Flag> Ꮡf) {
    ref var f = ref Ꮡf.Value;
    return Ꮡf.of(Flag.Ꮡc).Add(1);   // f.c.Add(1) — nested field-address box
}
```
The nested `Ꮡf.of(Flag.Ꮡc).of(Counter.Ꮡn)` chain resolves each level through `ж<T>.Value` (which honors a parent that is itself a field/array reference), so writes land on the real embedded field rather than a copy. A plain (non-capture) value method called through the same field — `f.c.Get()` — is left as a normal `f.c.Get()` value call.

This field-address routing applies only to **value** fields. When the field is itself a **pointer** — e.g. cpuProfile's `log *profBuf`, accessed as `cpuprof.log` where `cpuprof` is a heap-boxed global — its C# value is *already* a `ж<profBuf>` box, so a direct-ж method binds to it directly: `cpuprof.log.close()`. Taking the field's address (`Ꮡcpuprof.of(cpuProfile.Ꮡlog)`) would double-box to `ж<ж<profBuf>>` (CS1929). The heap-boxed-receiver routing recognizes that a field selector or indexed element whose own type is a Go pointer is already a box and skips the `&`-machinery for it. This discriminates a pointer *field* of a boxed global (already a box) from a deref'd pointer *parameter* (`s` in `s.Prev()`, a value alias whose box is `Ꮡs`): the latter is a bare identifier, not a selector/index, so it is correctly still routed through `Ꮡs`. The same exclusion applies when the pointer field is reached through a pointer **local** rather than a boxed global — `s := sl.mspan; s.gcmarkBits.bytep(…)` where `s` is a `*mspan` local — which otherwise routed through the pointer-local-field address path (`s.of(mspan.ᏑgcmarkBits)`); the field value `(~s).gcmarkBits` is already the `ж<gcBits>`. (Guarded by the `PointerFieldOfBoxedGlobal` behavioral test, covering both the boxed-global `cpuprof.log.write`/`.close` form and the pointer-local `s.log.push` form; runtime exercises both pervasively, e.g. `mspan.sweep`.)

The same applies when the value field belongs to a **package global** rather than a receiver — `ctrl.total.Add(5)` where `var ctrl controller` and `total` is an atomic field. The method's box address goes through the field-address machinery, `Ꮡctrl.of(controller.Ꮡtotal).Add(5)`, not a bare `Ꮡ` prefix on `ctrl.total` (which would bind to the box variable `Ꮡctrl`, whose value type has no `total` member → CS1061). This is the form runtime uses pervasively for `gcController`, `sched`, `memstats`, etc. The **method call itself** triggers heap-boxing the global: when a pointer-receiver method is called on a (possibly nested) value field of a package value global, the escape pass marks that global address-taken so its box exists — the call site needs the box even when the global is never explicitly `&`-addressed elsewhere. This is gated on the method being ж-only (a pointer receiver): a same-package method known to be capture-mode, **or** any pointer-receiver method whose package's capture-mode set is not locally available — the latter covers cross-package atomic methods (`func (x *Uint32) Store`), which are likewise emitted with only a box receiver, so a plain value/ref of the field cannot bind them (CS1929). The walk to the global root bails at any pointer hop (a field reached through a pointer already has a real address and is handled by the pointer-local / receiver paths), so a receiver/parameter field such as `f.c` is never disturbed. (Guarded by the `AtomicValues` behavioral test's global-atomic-field case; runtime exercises this for `prof.signalLock`, `trace.seqlock`, `scavenge.gcPercentGoal`, etc.)

It also applies when the receiver is an **indexed element** of such a field — `trace.stackTab[i].dump()` (boxed global) — where the element's address goes through the box-field accessor: `Ꮡ(trace.stackTab, i).dump()` for a slice field, or `Ꮡtrace.of(T.ᏑstackTab).at<E>(i).dump()` for an array field. The same routing covers an indexed element of an array/slice reached through a **pointer** — `bh.Value[i].Load()`, where `bh` is a pointer and the element is an atomic value — emitted `bh.of(T.Ꮡval).at<E>(i).Load()`. This is gated on the called method being **direct-ж** (a box receiver): an ordinary `[GoRecv] ref` method binds to an addressable element directly, so it is left as `container[i].method()` and only a direct-ж method (which truly needs the box) is routed — avoiding needless churn on the common case. (Guarded by the `IndexedElementDirectBoxMethod` behavioral test — a direct-ж method on an array-element-through-a-pointer-parameter, with mutation persistence verified; runtime hits this on `mprof`'s `bh.Value[i].Load()`/`.StoreNoWB()`.)

A capture-mode method called on a **value local of an inherently-heap type** — a named *slice*/*map*/*chan* — also forces the box, which `identHasHeapBox` otherwise refuses. An inherently-heap type is already a reference, so a var of it is normally *not* boxed even when it "escapes" (the escape pass marks every inherently-heap local escaping and returns early). But a capture-mode pointer-receiver method — internal/trace/internal/oldtrace's `orderEventList` (a named `[]orderEvent`) with heap.Interface `Push`/`Pop` that forward the receiver to `heapUp(h, …)`/`heapDown(h, …)` — is emitted with a `ж<orderEventList>` receiver, so a plain value cannot bind it (CS1929 — `var frontier orderEventList; frontier.Push(…)`). The escape pass therefore records the capture-mode reason **in that inherently-heap early-return branch** (the only place these vars are seen, before the general address-of scan), and `identHasHeapBox` honors it — emitting `ref var frontier = ref heap<orderEventList>(out var Ꮡfrontier)` so the calls route `Ꮡfrontier.Push(…)`/`Ꮡfrontier.Pop()` through the box. A named slice/map/chan with no capture-mode method called on it stays unboxed (already a reference — no churn). (Guarded by the `NamedSliceCaptureMethod` behavioral test — a named-slice value local with `*stack` `push`/`pop` that forward the receiver to helpers, mutated and read through the same box, output-compared vs Go.)

A capture-mode method called on a **value PARAMETER** boxes the parameter **at entry** — go/format's `format(…, cfg printer.Config)` calling `cfg.Fprint(&buf, fset, file)`, where `(*printer.Config).Fprint` is transitively direct-ж (its body calls the defer/recover-wrapped `fprint` on its own receiver), so its only emitted receiver form is the box `ж<Config>` and the raw value parameter cannot bind it (CS1929 ×2). Parameters are deliberately **never** fed through the full escape analysis, so the escape pass runs only narrow, named parameter checks (`markCaptureModeBoxedParams`) rather than the general escape walk — this one being `bodyCallsCaptureModeMethodOn`, the same predicate the local-var arms use. (The companion check, `objectAddressTaken`, was added later — see *An address-taken VALUE PARAMETER heap-boxes too* above; before it, a plain `&param` did use the `Ꮡ(value)` copy-box.) For a marked param the **signature renames the incoming value to the `ʗp` form** (the variadic-prologue rename convention) and the parameter preamble declares the boxed alias:

```csharp
internal static (slice<byte>, error) format(…, printer.Config cfgʗp) {
    ref var cfg = ref heap(cfgʗp, out var Ꮡcfg);
    …
    cfg.Indent = indent + indentAdj;        // body writes hit the boxed storage…
    var err = Ꮡcfg.Fprint(…);               // …the same storage the callee mutates through the receiver
```

Entry-time boxing is the load-bearing choice: Go auto-addresses the parameter (`cfg.Fprint(…)` ≡ `(&cfg).Fprint(…)`), so a body write **before** the call (`cfg.Indent = …`) must be seen by the callee, and the callee's writes through the receiver pointer must be seen by the rest of the body — while the **caller's** argument stays untouched (by-value parameter). A call-site `Ꮡ(cfg)` copy-box compiles but silently drops the callee's writes for the rest of the function. An ARRAY param folds its Go by-value clone into the box init (`ref var b = ref heap(bʗp.Clone(), out var Ꮡb);` — the plain `b = b.Clone();` preamble line is skipped), and an inherently-heap-typed param records the capture-mode box reason exactly like the value-local arm above. Beyond this trigger and the address-taken one, a param that leaks into `identEscapesHeap` some other way — a mixed `data, pc, line := …` define re-uses the param object, so the define walker escape-analyzes it (debug/gosym's `slice`) — keeps its historical unboxed emission (`paramNeedsHeapBox` re-verifies the predicate against the declaring ident). Whole-stdlib reconvert diff: exactly go/format's `internal.cs` changed, nothing else. (Guarded by the `CaptureModeValueParam` behavioral test — a defer-promoted direct-ж method plus a transitively-promoted one called on a value parameter, with a pre-call write observed by the callee, callee writes read back after, and the caller's copy proven untouched, output-compared vs Go — and by the `CaptureModeValueParamLib`/`CaptureModeValueParamUser` cross-package pair mirroring the format→printer shape: a foreign `Config` value param, `Fprint` → defer/recover `fprint` transitive promotion, trace accumulation across two calls proving write-visibility through the foreign `ж<Config>` extension.)

When the same function **also contains a func literal or defer that references the boxed parameter**, the in-lambda references must route **through the box** — the capture analysis marks such a param box-ref (the same arm family as a deref'd pointer parameter, whose `ref var p = ref Ꮡp.Value` alias shares the exact shape). The boxed param's Go name is a `ref`-local alias, which a C# lambda cannot capture (CS8175), and the general capture-snapshot fallback (`var tʗ1 = t;` before the lambda) compiles but **divorces the closure from the boxed storage** Go shares between the closure and the direct-ж callee: a closure read misses the callee's writes through the receiver pointer, a closure write is invisible to the callee, and a deferred closure observes entry-time values instead of return-time state. With the box-ref mark, a closure read emits `var get = () => Ꮡt.Value.total;`, a closure write `Ꮡt.Value.total += 100;`, and a deferred observer `defer(() => { (result, log) = (Ꮡt.Value.total, Ꮡt.Value.log); }, ref ᒐ);` — the box `Ꮡt` is a plain `ж<T>` local, captured by reference, so every reference (body, closure, callee) hits the one boxed storage, matching Go's one-parameter-variable semantics. A **deferred direct-ж method value on the param itself** (`defer t.Add(n)`) needed no change — it already routes through the box (`defer(Ꮡt.Add, n, ref ᒐ)`), binding the receiver address at defer time exactly like Go. Whole-stdlib reconvert diff: **zero files** — no stdlib function composes a capture-mode-boxed param with a closure today, so the composition is user-code-facing and was guard-discovered. (Guarded by the `CaptureModeParamClosure` behavioral test — four compositions with write-visibility checks in both directions: a closure read that must see the callee's later write, a closure write the callee must observe (and vice versa), a deferred closure reading return-time state, and a deferred method value whose writes a sibling deferred observer reads; each output-compared vs Go, with the caller's copy proven untouched. Under the pre-fix snapshot emission all four compiled and produced wrong values.)

Entry-time boxing extends to a **function literal's own value parameter** — the original coverage walked only `*ast.FuncDecl` params, so `f := func(t Tally, m int) {…; t.Add(m); …}` rendered the raw `Tally` value against `Add`'s only `ж<Tally>` receiver form (CS1929). The escape pass marks literal params with the same one-narrow-predicate check as declaration params (walking `FuncLit` nodes **before** the define walk, so a mixed `t, y := …` re-use cannot pre-empt the verdict; a leaked-but-not-capture-mode param keeps its historical unboxed emission via the same declaring-ident re-verification). The literal's signature takes the incoming value under the `ʗp` name and its **first block statement** is the boxed re-declaration — the exact preamble form, injected before the single-return collapse (which it thereby suppresses, correctly keeping the body a block):
```csharp
var f = (Tally tʗp, nint m) => {
    ref var t = ref heap(tʗp, out var Ꮡt);
    t.total++;                    // body writes hit the boxed storage…
    Ꮡt.Add(m);                    // …the same storage the callee mutates
    return (t.total, t.log);
};
```
This applies uniformly to every literal form: an assigned literal, a call argument, a `defer func(t Tally) {…}(x)` / `go …` argument-passing target (each deferred/goroutine run boxes its own copy at entry), and — unlike the variadic prologue, which excludes them — an **IIFE**, whose names-only parameter list emits the `ʗp` name so the rebinding composes with the delegate cast. A literal with both a variadic tail and a boxed param stacks the two `ʗp` prologues (variadic slice first, matching the declaration preamble order). A **nested closure** over the literal's boxed param takes the box-ref route (never a value snapshot, which compiled but orphaned the callee's writes — `var tʗ1 = t; tʗ1.Add(9)` lost both directions of write-visibility), while the literal's **own body** keeps the plain ref-alias renders above (`t.total++`, not `Ꮡt.Value.total++`): a box-ref var whose declaring literal is the lambda currently being converted renders plain, since its box and alias are locals of that very lambda — only genuinely nested lambdas read through the box. Whole-stdlib reconvert diff: **zero files** — no stdlib literal calls a capture-mode method on its own value param today, so this is user-code-facing and guard-discovered. (Guarded by the `CaptureModeFuncLitParam` behavioral test — assigned, IIFE, deferred-argument, nested-closure, and variadic-composition shapes, each with write-visibility checked in both directions and the caller's copy proven untouched, output-compared vs Go.)

And it applies when the field belongs to a **pointer local** — `h.s.inc()` where `h` is a `*holder` local and `inc` has a pointer receiver. A pointer local holds the box `ж<holder>` directly, so the value `~` dereference of the field (`(~h).s`) is an rvalue; the `[GoRecv]` method needs an addressable receiver (CS1510 on the generated `ref`). The field's box address is taken instead — `h.of(holder.Ꮡs).inc()` — binding the `ж` overload. (A pointer *parameter* is deref-aliased to a value, so `p.s.inc()` already works without this and is left alone. This is the form runtime uses for `(*c).gp.set(…)` / `.cas(…)` in coro.)

Finally, the same rvalue problem occurs when the field belongs to a pointer reached through *another field* — `o.h.wait.add(…)` where `o.h` is a `*holder` field and `wait` is a value (atomic) field. `o.h` dereferences to an rvalue, so `(~o.h).wait` is not addressable. The receiver is routed through the box-field accessor `o.h.of(holder.Ꮡwait)`, which aliases the **real** field storage — *not* a `Ꮡ(value)` copy, which compiles but silently boxes a copy so the atomic write is lost (a behavioral bug, not a compile error). Both the explicit address form (`&o.h.wait`) and a pointer-receiver method call on the field are routed this way. This is deliberately scoped to a base that is itself a *field selector*: a bare-ident base is the method's own receiver or a deref'd pointer *parameter* (both emitted as an addressable `ref`, so `f.c.Get()` binds directly — routing them through `&` would emit `Ꮡf.of(…)` but a value-ref receiver has no `Ꮡf` box) or a pointer *local* (handled above). (Guarded by the `AtomicFieldThroughPointer` behavioral test — a mutate-then-read proves the real field is updated, not a copy; runtime exercises this for atomic fields reached through pointer chains such as `sgp.g.selectDone.CompareAndSwap` and `gp.m.mLockProfile.recordLock`.)

The base may also be a pointer **rvalue** — a pointer-returning **call** (`getg().schedlink.set(…)`, `q.tail.ptr().schedlink.set(…)`, `Δp.chunkOf(ci).scavenged.setRange(…)`, `getg().m.p.ptr().wbBuf.get2()`) or a pointer **element index** (`batch[i].schedlink.set(…)`). Go auto-derefs the pointer to reach the value field, so the converter renders the read as `(~rvalue).field`; the `~` deref is an rvalue, so a pointer-receiver method on it cannot bind (`CS1510` on the generated `ref`). Unlike a deref-aliased *parameter* (whose box is `Ꮡp`) or a *field* deref (handled above), the call/index value **already is** the `ж<T>` box, so the receiver is materialized straight through it via the box-field accessor — `getg().of(g.Ꮡschedlink).set(…)`, `batch[i].of(g.Ꮡschedlink).set(…)` — never a `Ꮡ(value)` copy (which would lose the write). The routing is scoped to a base that is **not** an ident and **not** a field selector (those are the param/receiver/local/field cases above) and is **not a type conversion**: a conversion `(*T)(p)` renders as a C# *cast* (`(ж<T>)(uintptr)(…)`), a low-precedence form on which a trailing `.of(…)` would mis-bind to the inner operand, so a pointer-reinterpret keeps its existing `Ꮡ(…)` form (the runtime-unsafe S1 territory). (Guarded by the `PointerRvalueFieldReceiver` behavioral test — a pointer-receiver method on a value field reached through a returning call, a method-call chain, and a pointer-element index, each with write-through verified; runtime exercises this for `guintptr.set` via `getg()`/`batch[i]`/`q.tail.ptr()`, `pallocData.setRange` via `chunkOf`, and `wbBuf.get2`/`discard` via `getg().m.p.ptr()`.)

**A TYPE-ASSERTION base is a pointer rvalue too (2026-07-31).** The shape list that admits a base into
that box-field routing enumerates ident / selector / call / index / star, and a type assertion is none
of them — so `&c.(*UDPConn).conn` (net `udpsock_test`, reaching the promoted `conn.Write` through an
asserted `PacketConn`) dropped to the `Ꮡ(value)` copy-box fallback and named a `.conn` member that
`ж<UDPConn>` does not have (**CS1061**; had it bound, it would have written into a copy). The list is
about C# **precedence**, not about which node kinds have happened to come up: a base whose rendering is
postfix chains `.of(…)` cleanly, and a type assertion always renders as the postfix `c._<ж<UDPConn>>()`,
which *is* the box. Only the type-CONVERSION `CallExpr` stays excluded, for the cast-precedence reason
stated above. `exprIsValueFieldOfPointerRvalue`, the sibling predicate that decides the *routing*,
already accepted a type assertion through its default arm — so the two now agree rather than one
routing a shape the other could not render. This is the address-of copy-boxing family's next uncovered
**base** shape; the pattern of that family is that each fix covers one base shape, so the next
uncovered one is worth looking for rather than waiting for. (Guarded by the
`PointerRvalueFieldReceiver` extension — `iface.(*node).s.set(55)`, with the write read back through
the original pointer.)

It has a **second consumer, in an already-banked package**, found by the validated sweep rather than
predicted: `compress/flate`'s `flate_test.go` reaches `dict.availWrite()` through an asserted
`*decompressor`, and the old emission copy-boxed it —

```csharp
Ꮡ((~r._<ж<…decompressor>>()).dict).availWrite()                      // copy
r._<ж<…decompressor>>().of(…decompressor.Ꮡdict).availWrite()         // the real field
```

— which `flate` survived only because `availWrite` is a **read**. That is this family's signature
exactly: the copy gives the right answer until someone writes through it, and the documented
"faithful for reads" caveat is a latent wrong answer with a timer on it. `compress/flate` re-validates
at its banked 64/64 with the corrected emission.

The bare-ident-base exclusion above holds **only for `[GoRecv] ref` methods** (which bind on the addressable value alias directly). A **direct-ж** (box-receiver) method — `func (s *scavengeIndex) find(…)` and the like, emitted with a `ж<T>` receiver — needs the *box*, so calling it on a value field-chain rooted at a deref-aliased pointer **parameter or (direct-ж) receiver** is `CS1929`: `Δp.scav.index.find(force)` (root `p`, a `*pageAlloc` receiver), `mp.trace.seqlock.Load()` (root `mp`, a `*m` parameter), `h.userArena.readyList.remove(s)`. These are routed through the box-field accessor too — `Ꮡp.of(pageAlloc.Ꮡscav).of(pageAlloc_scav.Ꮡindex).find(force)` — never a `Ꮡ(value)` copy (which would lose an atomic write). The `&`-machinery recurses through the value field-chain to the param/receiver box: `&Δp.scav.index` builds `Ꮡp.of(…).of(…)`, where the box base is the **raw** parameter name (`Ꮡp`, not the shadow-renamed `ᏑΔp` — a deref param `p`→`Δp` is `ref var Δp = ref Ꮡp.Value`, box `Ꮡp`). The routing is gated to direct-ж so a `[GoRecv] ref` method on the same chain keeps binding directly (no churn); a receiver root additionally requires the *enclosing* method to be direct-ж (only then does its receiver box `Ꮡrecv` exist). (Guarded by the `FieldChainBoxReceiver` behavioral test — a direct-ж method on a value field-chain rooted at a pointer parameter and at a direct-ж receiver, both with write-through verified; runtime exercises this pervasively for `scavengeIndex`/`mSpanList`/`timers` methods and `m.trace` atomic fields.)

For the **receiver-root** case, the enclosing method only *becomes* direct-ж through the capture-mode pre-pass's transitive fixpoint: a pointer-receiver method that calls a direct-ж method on a value field-chain of its own receiver — `func (p *pageAlloc) free(…) { … p.scav.index.free(…) }` — is promoted to direct-ж so its receiver box `Ꮡp` exists for the routing above. This detection walks the **full** value field-chain `recvName.f1.…fn.method` (every hop a value, non-pointer field), not just one level: `p.scav.index.free(…)` roots `free` at the receiver `p` through two value fields (`scav`→`index`). A one-level chain (`b.u.Load()` on an embedded atomic) was already detected; the multi-level walk generalizes it. A pointer field anywhere in the chain stops the walk — that subexpression is already a box and roots the call elsewhere (the pointer-field paths above), so it must not trigger promotion. The promotion is transitive: once `pageAlloc.free` is direct-ж, its caller `func (h *mheap) freeSpanLocked(…) { … h.pages.free(…) }` is in turn promoted (now calling a direct-ж method on `h.pages`), and so on up the call graph until a root holding the value through a real box/pointer. (The multi-level receiver-root promotion is covered by the `FieldChainBoxReceiver` test's `deep.bumpDeep` case — `d.mid.c.inc()`, a direct-ж `inc` on a two-level value field-chain of a receiver with no other direct-ж trigger, write-through verified; runtime exercises it on `pageAlloc.free`/`freeSpanLocked`.)

### `unsafe.Alignof` / `unsafe.Offsetof` name a TYPE, resolved through `go/types`
Go defines `Sizeof`, `Alignof` and `Offsetof` against the **static type** of their operand — and never
evaluates that operand (all three are compile-time constants for any type of non-variable size). golib
matches that shape: `Alignof(Type, string? fieldName = null)` and `Offsetof(Type structType, string
fieldName)` take a `System.Type`, so the converter has to turn the Go operand into a type argument.

It now does that from `go/types`: **`unsafe.Alignof(x)` emits `@unsafe.Alignof(typeof(T))`** for `T` the
C# rendering of the operand's static type — one rule for *every* operand shape, because Go's
`Alignof(s.f)` is the required alignment of the **field's own type**, which is exactly what golib's
two-argument overload resolves to anyway. **`unsafe.Offsetof(s.f)` emits `@unsafe.Offsetof(typeof(S),
"f")`**, where `S` comes from `types.Selection.Recv()` with any implicit pointer dereference stripped,
and a *promoted* field is walked down its embedding chain so the offset is measured against the struct
that declares it (Go's rule: relative to the immediately enclosing struct). The field name is the
emitted identifier with any keyword escape removed, since reflection sees `@out` as `out`.

The shape was previously derived by splitting the **converted C# text** on `.` and reading the pieces as
if they were a Go field selector — one part meant `x`, two meant `s.f`, anything else warned and fell
through to an emission that cannot compile. That mistakes any dotted *rendering* for a selector and
corrupts every operand that is not literally an identifier or a one-level selection: a conversion
operand renders with a leading cast, so `unsafe.Alignof(uint32(0))` became `(uint32)0.GetType()`, which
C# parses as `(uint32)(0.GetType())` — `CS0030: Cannot convert type 'System.Type' to 'uint'`, and the
sole build blocker on `crypto/md5` (its `benchmarkSize` alignment probe); a `ж` dereference `Ꮡx.Value`
read as struct `Ꮡx` with field `Value`; and a two-level `cpu.X86.HasAVX` was rejected outright.
`.GetType()` was also the wrong instrument on its own terms — it reports the **dynamic** type of a boxed
or interface-typed operand where Go uses the static one, and it evaluates the operand, which Go does
not. (`unsafe.Sizeof` was unaffected: it emitted the generic `@unsafe.Sizeof(x)`, whose type argument C#
infers.) Guarded by the `UnsafeOperations` behavioral
test, extended with a conversion operand, an index operand, a selector through a pointer, a two-level
selector, and a field whose name is a C# keyword; all output-compared vs `go run`.

**This shape is now the FALLBACK, not the normal path** — expression sites fold to the constant (next
section), and only a variable-size operand still reaches the `typeof(T)` emission. The promoted-field
rule stated above is also *wrong about Go*, and the fold supersedes it: Go measures a promoted field
against the **operand** struct, not against the struct that declares it.

### `unsafe.Sizeof` / `Alignof` / `Offsetof` FOLD to a constant at expression sites
Go computes all three at compile time from the operand's **static type**, never evaluates the operand,
and yields a **typed `uintptr` constant** for any operand type of non-variable size. *Declaration*
sites have always emitted that constant — `internal static uintptr offsetX86HasAVX => /*
unsafe.Offsetof(cpu.X86.HasAVX) */ 66;` in `runtime/cpuflags.cs`. **Expression sites now emit the same
form**, so one Go construct has one behavior:

```go
var hdr Header32
data := make([]byte, unsafe.Sizeof(hdr))
// …
f.Type = Type(bo.Uint16(data[unsafe.Offsetof(hdr.Type):]))
```
```csharp
var data = new slice<byte>((nint)(/* unsafe.Sizeof(hdr) */ (uintptr)52));
// …
f.Value.Type = ((Type)bo.Uint16(data[(int)(/* unsafe.Offsetof(hdr.Type) */ (uintptr)16)..]));
```

The value comes from `go/types`, which folds against the `types.Sizes` for the **loaded target
`GOARCH`** — the Go compiler's own layout rules — so the emitted number is what the *Go* program
computes, not a measurement of the emitted C#. The literal keeps its `uintptr` type because Go's
constant is typed: a bare number would let `uadd := unsafe.Sizeof(*t)` infer C# `int`, and an `int`
*variable* has no implicit conversion back to `nuint` (`internal/abi`'s `FuncType.InSlice` hands it to
a `uintptr` parameter — CS1503). The cast is inert everywhere else: C#'s constant-expression conversion
would have bound a bare literal anyway, and a cast binds tighter than every binary operator, so no site
needs extra parentheses.

Three things this fixes, beyond removing a reflection call from a construct Go settles at compile time:

- **A latent throw.** golib's `Sizeof` rides `Marshal.SizeOf<T>`, which **throws** for a non-blittable
  `T` — and a converted Go struct is non-blittable as soon as it holds a `slice<T>`, `@string`,
  interface, or `ж<T>` field. `debug/elf`'s `Header32` holds an `array<byte>` (a `byte[]` inside), so
  its `unsafe.Sizeof(hdr)` answered **56** where Go says 52 (a 4-byte-too-long header read), and the
  reader's own `Section`/`Prog` — embedded struct + `io.ReaderAt` + `ж<SectionReader>` —
  **throw** `ArgumentException: … cannot be marshaled as an unmanaged structure`. `runtime.mapiterinit`'s
  `unsafe.Sizeof(hiter{})/goarch.PtrSize != 12` guard sits in front of every map range, and `hiter`
  holds four `ж<…>` fields.
- **Semantic fidelity.** A reflection answer measures the *CLR-marshalled* layout, which differs from
  Go's whenever golib's field representation differs — `string` is 16 bytes in Go/amd64, `@string` is a
  managed struct. `debug/elf` uses these values as **on-disk format offsets**; they must be Go's.
- **Promoted fields.** Go measures `unsafe.Offsetof(e.count)` against the **operand** struct: with
  `count` at 8 inside an embedded `Padded` that itself sits at 8, the answer is **16**. The reflection
  form could see only one hop of the embedding chain; `go/types` folds the whole path.

A **variable-size** operand still emits the run-time form (with a converter warning naming the site),
because Go itself does not fold it: since Go 1.18 the operand may be **type-parameter-typed**, and the
call is then not a constant. Four such sites exist in the stdlib — `slices.Compact`
(`unsafe.Sizeof(a[0])` on `S ~[]E`), `internal/saferio` (`unsafe.Sizeof(v)` on `E`), and
`runtime/minmax` (×2) — and they are why golib's `@unsafe` run-time forms are retained rather than
deleted.

Measured over the full stdlib (seeded A/B reconvert, Go 1.23.1, `windows/amd64`): **262 expression
sites folded across 61 files in 16 packages** — `runtime` 129, `debug/elf` 70, `syscall` 17,
`internal/poll` 13, then a long tail; declaration sites byte-identical. Design record:
[`docs/phase4/DESIGN-unsafe-constant-folding.md`](phase4/DESIGN-unsafe-constant-folding.md). Guarded by
`UnsafeOperations`, extended with all three builtins in call-argument, arithmetic, comparison,
assignment and compound-assignment, and `make`-size positions, over structs whose Go layout is
padding-, embedding- and array-sensitive (and non-blittable once converted); output-compared vs
`go run`.

### The run-time `unsafe.Sizeof` answers through Go's layout rule, not the CLR's marshaller

The folding arc above removed the reflection call from every site Go itself settles at compile time,
and named what was left: the handful of operands whose type is a **type parameter**, which Go's own
spec calls variable-size and does not fold either. Those kept riding `Marshal.SizeOf<T>` — and there
the latent throw the folding arc had just designed around was not latent at all, because a type
parameter binds at run time to exactly the shapes `Marshal.SizeOf` refuses: a generic type
(`ж<Section>`, `slice<T>`) raises *"The specified Type must not be a generic type"*, and a struct
holding a managed reference raises *"cannot be marshaled as an unmanaged structure"*.

Three packages died on it at once, all through the same one line — `internal/saferio.SliceCap[E]`,
which asks `unsafe.Sizeof(*new(E))` only to decide how large a chunk it may pre-allocate:
`debug/macho` (E = the `Load` **interface**), `internal/xcoff` (E = `ж<Section>`), and
`go/internal/gccgoimporter` through `debug/elf` (E = `ΔSection`, a struct over an embedded header, an
`io.ReaderAt` and a `ж<SectionReader>`).

So the run-time form now answers through **`GoReflect.GoSizeOf`** — the same Go-layout walk the
reflection bridge already stamps into a descriptor's `Size_`, and the same one `reflect.Type.Size()`
reads:

```csharp
public static uintptr Sizeof<T>(T x) {
    nint size = GoReflect.GoSizeOf(typeof(T), GoReflect.ArrayDimsOfValue(x));
    return size >= 0 ? (uintptr)size : (uintptr)Marshal.SizeOf<T>();
}
```

`typeof(T)` is Go's rule verbatim — `Sizeof` is defined against the operand's **static** type, and the
converter's inferred type argument is that type. Dims come from the live value because `array<T>`
carries its Go length in the instance, not the type. `Marshal.SizeOf` stays as the fallback for the
shapes `GoSizeOf` declines (`-1`: an array whose length nothing can reveal, a struct holding such a
field), so no operand that resolved before stops resolving.

This makes the answer *correct* as well as non-throwing, which matters beyond the three packages:
`Marshal.SizeOf` reports a **bool** as 4 bytes where Go says 1, so any struct containing one was
already measured wrong — silently, at the sites the folding arc could not reach. A Go size now has
one definition in the runtime rather than two (the unification `golib/GoReflect.TypeLayout.cs` had
recorded as deferred pending a named consumer). Corpus reach is small by construction: **7 run-time
call sites**, against 283 folded ones.

### Converting a Go pointer to `unsafe.Pointer`
`unsafe.Pointer` is the golib class `unsafe_package.Pointer : ж<uintptr>` (a numeric address wrapper). A `uintptr`/`unsafe.Pointer` argument converts through the implicit `uintptr ↔ Pointer` operators, but a **Go pointer** argument (`*T`, emitted as the managed box `ж<T>`) has no such conversion — a plain cast `(@unsafe.Pointer)(ж<T>)` is `CS0030` (when `T` is unrelated to `uintptr`) or a runtime `InvalidCastException` (the base→derived downcast `(@unsafe.Pointer)(ж<uintptr>)` compiles but the object is a plain `ж<uintptr>`, not a `Pointer`). So `unsafe.Pointer(ptr)` for a pointer `ptr` is emitted through the golib helper that pins the pointed-to storage:
```go
func (u *UnsafePointer) Load() unsafe.Pointer { return Loadp(unsafe.Pointer(&u.value)) }
```
```csharp
public static @unsafe.Pointer Load(this ж<UnsafePointer> Ꮡu) {
    ref var u = ref Ꮡu.Value;
    return (uintptr)Loadp(@unsafe.Pointer.FromRef(ref (Ꮡu.of(UnsafePointer.Ꮡvalue)).Value));
}
```
> The resulting numeric address is **not GC-stable** — the same caveat that applies to every `unsafe.Pointer`-as-`uintptr` use; the runtime intrinsics that consume it (e.g. `Loadp`, `StorepNoWB`) are assembly stubs, so this conversion is about producing compilable C#, not GC-correct pointer arithmetic. (The reinterpret pattern `*(*U)(unsafe.Pointer(&x))` is handled separately and is not affected.)

**A NIL pointer converts to address 0, not a throw.** golib's `ж<T> → uintptr` (and `ж<T> → void*`) operator takes the pointed-to storage's address via a `fixed` block — but a **nil** box has no storage to pin, so `&value.Value` dereferences it and throws. Go's `uintptr(unsafe.Pointer(nil))` is simply **0**, and the syscall wrappers pass nil pointers exactly this way: `syscall.Write` hands `writeFile` a nil `*Overlapped` for a synchronous write, then passes `uintptr(unsafe.Pointer(overlapped))` (= 0) to the `SyscallN` trampoline. The operators now return `0`/`null` for a nil box before pinning — so any converted `os.Stdout.Write` (hence `fmt.Println`) whose stdout is a pipe reaches the OS `WriteFile` and prints, instead of crashing on the nil-`overlapped` argument. (Guarded by the `NilPointerUintptr` behavioral **output** test — `uintptr(unsafe.Pointer(nilPtr)) == 0` and a non-nil control, vs Go.)

**A PACKAGE-SCOPE `uintptr(unsafe.Pointer(...))` must not crash the converter.** The `unsafe.Pointer` conversion path has a special case that rewrites `unsafe.Pointer(arg)` into the ref-based extension call `(uintptr)@unsafe.Pointer.FromRef(ref arg)` when the *enclosing function is a pointer-receiver method* whose single argument aliases the receiver (pointer-receiver methods are emitted as ref-based extension functions, so the pointer must be reconstructed from a `ref`). That test read `v.currentFuncSignature.Recv()` **unconditionally** — but a **package-level** `var` initializer is converted with no enclosing function, so `currentFuncSignature` is `nil` and the receiver probe nil-panicked *during conversion* (`go/types.(*Signature).Recv`). The special case can never apply at package scope — there is no receiver — so the fix guards it with `v.currentFuncSignature != nil` (the same idiom `convUnaryExpr` and `captureModeOperations` already use), which falls through to the ordinary emission: `var gPtr uintptr = uintptr(unsafe.Pointer(&global))` → `(uintptr)Ꮡglobal` (it read `(uintptr)new @unsafe.Pointer(Ꮡglobal)` until the dead-wrapper peephole below), identical to the in-function non-receiver form the corpus already produces. This is what blocked `cmp`'s Phase-4 validation: `cmp_test.go` declares `var nonnilptr uintptr = uintptr(unsafe.Pointer(&negzero))` and `var nilptr uintptr = uintptr(unsafe.Pointer(nil))` at package scope, and the converter crashed before emitting a line.

**A null `Pointer` *reference* (from `unsafe.Pointer(nil)`) also converts to 0.** Distinct from the nil-*box* case above: the untyped `nil` literal in `unsafe.Pointer(nil)` renders as `(@unsafe.Pointer)default!`, and `default` of the reference type `Pointer` is a C# **null**, not a `ж<T>` box. golib's `Pointer → uintptr` operator then dereferenced `value.Value` and threw `NullReferenceException` — even though `Pointer`'s own `==`/`!=` operators already treat a null reference as nil (`value?.IsNull ?? true`). The `uintptr` and `void*` conversion operators now honor that same null-tolerance (`value is null ? 0/null : value.Value`), so `uintptr(unsafe.Pointer(nil))` yields 0 whether the nil arrives as a nil box or a null `Pointer` reference. (Both the package-scope crash and this null-reference conversion are guarded by the extended `NilPointerUintptr` behavioral **output** test — package-level `var gPtr = uintptr(unsafe.Pointer(&global))` (non-zero) and `var gNil = uintptr(unsafe.Pointer(nil))` (0), vs Go; the pre-fix converter panics on the package-scope declaration and, once past that, the pre-fix golib NREs on `gNil`.)

**A pointer to a Go fixed array resolves to the array's DATA, pinned across the FFI call.** The other half of that `fixed`-block operator is wrong for a `ж<array<T>>` (`unsafe.Pointer(&arr)` where `arr` is a Go `[N]T`): `&value.Value` is the address of the golib `array<T>` **struct wrapper** — which holds the backing `T[]` as a reference field, an offset, and a length — *not* the address of the array data, and the `fixed` releases it before the operator even returns. A native syscall handed that address writes over the wrapper's fields (clobbering the `T[]` reference), so a later `buf[i]` reads through a corrupted array and faults. This is exactly the go-isatty MSYS/cygwin-pipe probe: `IsCygwinTerminal` fills a `[262]uint16` with a `FILE_NAME_INFO` via `GetFileInformationByHandleEx(…, uintptr(unsafe.Pointer(&buf)), …)`, then reads `l := *(*uint32)(unsafe.Pointer(&buf))` (the `FileNameLength`) and slices `buf[2 : 2+l/2]` — the garbage `l` drove `array<uint16>.get_Item(Range)` off the end (`AccessViolationException`). The converted **fatih/color** sample went **empty on a pipe** because of it: fatih/color's `NoColor` probe evaluates `!isatty.IsTerminal(fd) && !isatty.IsCygwinTerminal(fd)`, so only a *pipe* (where `IsTerminal` is false, unlike a console, and `GetFileType` is `FILE_TYPE_PIPE`, unlike a file) reaches the faulting FFI call — file-redirect and real-console output were fine, matching the observed matrix. The operators now special-case a value that is a Go fixed array — an `IArray` that is **not** an `ISlice` (a `slice<T>`'s `&s` is its header, exactly as in Go, so slices stay on the value-slot path) — and return the pinned address of element 0 of the backing `T[]`, via a `PinnedBuffer` (a `GCHandle.Alloc(…, Pinned)`) cached on the box. The pin lives for the box's lifetime — so the syscall write lands in the real backing array and every managed read afterward (the `l` reinterpret and the `buf[2:]` slice) observes it — and is released when the box is collected (the `PinnedBuffer` finalizer frees the handle). This is a golib-only change (no emitted-code difference); the array-buffer-to-syscall pattern that previously faulted now runs, while non-array pointers keep the existing transient `fixed`-address behavior byte-for-byte. (Guarded by the `FixedArrayBufferPointer` behavioral **output** test — the `*(*uint32)(unsafe.Pointer(&buf))` read-back idiom, the array still readable through its own indexer afterward, and address-stability across repeated conversions, vs Go; end-to-end, the converted fatih/color sample now prints byte-identically to `go run` through a pipe.)

**EVERY managed address handed to native code is pinned for the pointer's lifetime — a `fixed` block cannot outlive its own statement (2026-08-03, r38-os-fin).** The entry above pinned the ONE case that had been proven to corrupt memory; the general case was left with the transient `fixed` address, under a soundness note in `syscall/dll_windows.cs` that called the window between capture and the trampoline's `calli` "short and allocation-free". It is neither, for a BLOCKING syscall: the window stays open for as long as the kernel takes. `os`'s `TestPipeEOF` parks in `ReadFile` on a pipe for 10 ms per read while the rest of a parallel suite allocates around it, and a gen0 collection in that window moves both the `*uint32` byte-count box `syscall.Read` passes and the caller's read buffer — measured directly, a `heap(new uint32(), out var Ꮡdone)` box and a `Ꮡ(buf, 0)` element pointer BOTH report a different address after one forced collection. The kernel then writes to neither: `done` stays 0, `syscall.Read` returns `(0, nil)`, and `internal/poll`'s `FD.eofError` turns that into a premature `io.EOF`. That is the whole of the row characterized as *"`bufio.Reader.ReadBytes` over a converted `os.Pipe` returns a premature `io.EOF`, and only under parallel load"*, and it explains its measured shape exactly — monotone in the parallelism level (0 of 4 at `-parallel 1`, 1 of 3 at 4, 5 of 5 at 8, 100% at the default), because more threads means more allocation means more collections inside the same 10 ms window; independent of finalizers, which a control had already ruled out. The buffer's half of the same defect writes 4 KB into freed heap, which is the moving-site `ExecutionEngineException` recorded beside it. **golib now pins before it reads an address**: `ж<T>`'s `uintptr`/`void*` operators call `EnsureStableAddress`, which takes a lifetime `GCHandle` on the ROOT storage the pointer names — a standard heap box pins its own value slot, an element reference pins the canonical backing array, a field reference recurses to the allocation that contains the field — on exactly the terms `pinnedArrayData` already used for the fixed-array case, and released when the box is collected. The enabling change is that a standard heap box's value STORAGE is now a one-element array for a `T` that contains no references (`ж<T>.m_slot`): a box is a class with reference fields and `GCHandle` refuses to pin anything that contains pointers, so the value had nowhere pinnable to live. It is allocated EAGERLY and never migrated — `heap<T>(out ж<T>)` hands the caller a `ref` alias before any address is taken (`ref var done = ref heap(new uint32(), out var Ꮡdone)`), so moving the storage on first address-take would leave that alias on the abandoned copy, which is this very bug one level down. A `T` that DOES carry references gets no slot and keeps the transient address: its C# layout is not a native layout either, so no syscall can meaningfully be handed its address — the change is additive, and `RuntimeHelpers.IsReferenceOrContainsReferences<T>()` is a JIT constant, so neither the branch nor the allocation costs such a box anything. This also makes Go's unsafe.Pointer RULE 3 (pointer arithmetic through `uintptr`) sound, which it silently was not. golib-only — no emitted-code difference. (Guarded by `src/tests/GolibTests/NativeAddressStabilityTests.cs`, a neutered-fix control across all four box kinds plus the reference-bearing negative case: with `EnsureStableAddress` removed every address assertion fails on the first forced collection. Operationally, `os`'s residual went from 13 rows to 3 in one change — `TestPipeEOF` and the whole child-stdout family, whose empty child output was the same premature EOF read through `exec`'s pipe.)

**A STRUCT handed to the kernel by address must be blittable — otherwise the wrapper is hand-owned.** The previous entry fixes a pointer to a fixed ARRAY; this is the same problem one level up, for a whole struct, and it has no golib-level answer. A generated syscall wrapper passes `uintptr(unsafe.Pointer(&s))` and the kernel writes the NATIVE record at that address. That is safe only when the converted struct's C# layout matches the native one — which it does for a scalar/handle struct (`SecurityAttributes` is two `uint32`s and a `uintptr`, so `CreatePipe`, and therefore `os.Pipe`, works through the ordinary converted wrapper), and never when a field is a golib `array<T>` (Go's inline `[N]T`) or a `ж<T>` (Go's pointer field): both are MANAGED REFERENCES occupying one word where Windows expects inline bytes or a raw address. The kernel then writes the native-sized record over a smaller managed object — corrupting the heap past its end and leaving fabricated object references in the reference-typed fields. **It does not fail at the call.** It fails at the next read of one of those fields, arbitrarily far away and with a diagnostic that names the wrong code: `syscall.GetTimeZoneInformation` writes 172 bytes of `TIME_ZONE_INFORMATION` (two inline `WCHAR[32]` name buffers) over a ~64-byte `Timezoneinformation`, and the crash surfaces as an `ACCESS_VIOLATION` inside `slice<ushort>..ctor` on `zoneinfo_windows.go`'s next `syscall.UTF16ToString(z.StandardName[:])` — so every converted program calling `time.Now().Weekday()` / `Location()` / `Local` on Windows died, in `time`, with no mention of `syscall`. The remedy is per-wrapper hand-ownership, not a converter or golib change: a `manualConversionFuncs` entry turns the generated wrapper into a placeholder, and a `*_impl.cs` companion supplies a blittable `[StructLayout(LayoutKind.Sequential)]` mirror (`fixed` buffers for the inline arrays, so they stay inline), a direct `[DllImport]`, and an explicit field-for-field copy back into the converted struct at the boundary — see `src/core/syscall/zsyscall_windows_impl.cs`, and `exec_windows.cs`'s `StartProcess`/`_STARTUPINFOEXW` for the first instance. **Verify at VALUE level, never at fault level:** a mirror with the wrong offsets returns garbage without crashing, so "it no longer faults" proves nothing. (Guarded by the `LocalTimeZone` behavioral **output** test, which compares the zone ABBREVIATION — which comes from the name buffers — the offset in seconds — from `Bias`/`StandardBias`/`DaylightBias` — and a fixed instant rendered through the local zone — which selects between them via `StandardDate`/`DaylightDate` — against `go run`.) A census of `src/core/syscall` finds 32 non-blittable converted structs and eleven wrappers passing one by address (an earlier count of ten collapsed the `findFirstFile1`/`findNextFile1` pair into a single row); the ones not yet fixed are latent and board-rowed rather than fixed speculatively, since each needs its own value-level verification. **`src/core/syscall` is not the class's boundary, and the class runs in BOTH directions.** `internal/syscall/windows` holds six more wrappers of the same shape, and where the kernel merely READS the record go2cs hands it the diagnosis above inverts: nothing is written over the managed object, so there is no delayed corruption — instead the native reader picks each field out of the C# storage at the NATIVE offset, and because the CLR auto-layouts a struct containing references (grouping them first) an ordinary integer field ends up under a pointer field. `internal/syscall/windows.SHARE_INFO_2` is 48 managed bytes against 56 native ones, and netapi32 reads `shi2_path` at offset 40 — which in C# is `MaxUses` (1) followed by `CurrentUses` (0) — so it dereferences the pointer value `1` and the process dies AT the call with `0xC0000005`; the same reordering makes `shi2_passwd` an 8-byte over-read past the end of the record. This shape is loud and immediate rather than distant, which makes it easy to misread as a regression in whatever changed most recently. Note also what is NOT the defect: a managed reference standing in for an `LPWSTR` is survivable on its own — it is a readable address, and a control that keeps the native field ORDER returns `ERROR_INVALID_NAME` instead of faulting — so the reordering, not the reference, is what a remedy must answer. `os`'s own `readdir` is the worked precedent for the read direction (`src/core/os/windows/dir_windows_impl.cs` walks the kernel's buffer at native offsets rather than reinterpreting it as the managed surrogate). The write direction has no answer yet where the wrapper receives an opaque `*byte`: `Reinterpret` correctly declines to alias a reference-bearing struct as `byte`, and the address route it falls back to has already discarded the managed identity that a field-for-field copy would need.

**Second and third members of the class: `findFirstFile1` / `findNextFile1` (2026-08-01).** The same seam over a bigger record — `WIN32_FIND_DATAW` is 592 bytes with `cFileName[260]` and `cAlternateFileName[14]` INLINE (520 and 28 bytes of storage), where the converted `win32finddata1` carries two one-word `array<uint16>` references — and the first member a real test suite actually *reached*: `path/filepath.EvalSymlinks` → `toNorm` → `normBase` asks `FindFirstFile` for the on-disk spelling of every path element, so the whole `EvalSymlinks` family took the C# test host down mid-run, which silently under-reports every verdict after it as an empty result. Both faces of the corruption appeared from that one package — an `IndexOutOfRangeException` inside `PinnedBuffer` where the clobbered reference still resolved to something (`normBase`'s `UTF16ToString(data.FileName[:])`), and an outright `ACCESS_VIOLATION` in `slice<ushort>..ctor` where it did not (`copyFindData`'s `src.FileName[..]`). Only the two `*1` wrappers are hand-owned: Go itself puts the native-layout boundary exactly there — `syscall_windows.go`'s `FindFirstFile` allocates a `win32finddata1`, calls the wrapper, then `copyFindData`s out — so the public `FindFirstFile`/`FindNextFile` and `copyFindData` above them are pure Go logic and convert faithfully. Two details generalize to the next member of the class: the *name* argument is pinned with a `fixed` block wrapped around the call rather than handed golib's TRANSIENT `ж`→`uintptr` address (`Value` on an element box resolves to a ref INTO the caller's backing array, so `fixed` pins that array and the whole NUL-terminated name stays contiguous behind the pointer for the call); and both inline buffers are copied WHOLE, NULs included, because Go reads them as `UTF16ToString(data.FileName[:])` — which stops at the first NUL — while the SAME `win32finddata1` is reused across every `FindNextFile` of an enumeration, so a copy that stopped at the terminator would leave the previous entry's runes behind it. `Reserved0` (offset 36) is copied verbatim but deliberately not asserted: Windows documents it as the reparse-point tag only when `FileAttributes` carries `FILE_ATTRIBUTE_REPARSE_POINT` and UNDEFINED otherwise, so a reparse-free tree has no stable value to compare — its offset is instead pinned from both sides by the verified `FileSizeLow` (32) and `FileName` (44), which leave it and `Reserved1` the only eight bytes between them. (Guarded by the `FindFirstFileData` behavioral **output** test, which builds a purpose-made tree and compares, per entry, the long name in both ASCII and non-ASCII — `FileName` at 44 — the 8.3 short name — `AlternateFileName` at 564 — the directory bit — `FileAttributes` at 0 — the byte size — `FileSizeLow` at 32 — and a DISTINCT fixed instant stamped per entry — `LastWriteTime` at 20 — plus `ERROR_FILE_NOT_FOUND` for a missing file, the `ERROR_NO_MORE_FILES` end of an enumeration, and `filepath.EvalSymlinks` restoring canonical case through the whole `path/filepath` path, all against `go run`.)

**Fourth member, and the first carrying TWO defects: the sockaddr family (2026-08-11, lane L10).** `net.Listen` on Windows is what forced this one, and it never even reached the seam — it died one layer earlier. Go writes the port in network byte order through a two-byte alias over the raw struct's port field, `p := (*[2]byte)(unsafe.Pointer(&sa.raw.Port))`, and the auto conversion of that rebuilds an `array<byte>` from a raw address, which materializes `default(array<byte>)` — a LENGTH-ZERO array — so `p[0]` panicked with `index out of range [0] with length 0` (golib `array.cs:280` via `syscall_windows.cs:881`). An `array<T>` is a managed container with its own header, not two inline bytes, so no address reinterpret can ever produce one; the encoders are hand-owned and write the field arithmetically instead, leaving `raw` in exactly the state Go leaves it. Behind that sat the ordinary form of this class: `RawSockaddrInet4`'s `Addr [4]byte` / `Zero [8]uint8` are `array<byte>` managed references, so `unsafe.Pointer(&sa.raw)` names a ~24-byte object with object references where Windows wants a 16-byte `sockaddr_in` with the octets inline — the case golib's own `ж.cs` describes when it explains why a reference-bearing pointee gets no pinnable storage ("such a value's C# layout is not a native layout either, so no syscall can meaningfully be handed its address"). **Two departures from the precedent above are worth cribbing.** The mirror is a LOCAL at each call site rather than a field the way `Timezoneinformation`'s is: a sockaddr's native image is needed for the duration of one call, and a stack buffer is trivially stable for exactly that long, where a managed field's address would need a pin whose lifetime nothing owns. And no new `[DllImport]`/`[LibraryImport]` is declared at all — golib models `unsafe.Pointer` as a box over a plain address (`unsafe`'s `Pointer : ж<uintptr>`, whose `uintptr` operator returns the stored address), so the package's OWN generated `bind`/`connect`/`connectEx` wrappers already accept any address and were never the broken part; handing them the mirror's address reuses their errno handling verbatim and keeps the hand-owned surface to the layout translation, which is the only thing that was wrong. `Getsockname`/`Getpeername` are the exception, and for a precise reason: their generated wrappers take a typed `ж<RawSockaddrAny>` rather than an address, so those two call the package's `Syscall` trampoline directly, mirroring the generated wrappers' error handling. (Guarded by the `SockaddrRoundTrip` behavioral **output** test — `socket`/`bind`/`getsockname`/`listen`/`connect`/`getpeername` on loopback over both IPv4 and IPv6 — which prints kernel-derived values and cross-checks them rather than checking for absence of a fault: the client's `getpeername` must equal the listener's bound address field for field, closing encode → kernel → decode → encode → kernel → decode. Ephemeral ports are never printed, only whether the two ends agree about them, so the output is host-independent.)

**The DECODE joined the family three days later, and only because a CONVERTER capability landed first (2026-08-14, netpoll lane S2b).** `(*RawSockaddrAny).Sockaddr` carries the same port alias as the encoders and panics identically, but L10 left it auto-converted on measurement rather than on effort — its body held the only `ΔSockaddr` casts in the package, so displacing it dropped the three `GoImplement<…>(Pointer = true)` records and `net` minted duplicates (the trap written up in the next paragraph). Once `recordSamePackageImplements` began recording the POINTER method set the records stopped depending on that body, the hand-own became available, and `net`'s ACCEPT path is what wanted it: it decodes the `GetAcceptExSockaddrs` output through this one method, the single route to a `Sockaddr` that the hand-owned `Getsockname`/`Getpeername` do not cover. **Its shape differs from every other member of this class, and that is the transferable part.** The others translate a MANAGED struct into a native image to hand the kernel; this one runs in the opposite direction with no kernel call in it at all — it FLATTENS the managed `RawSockaddrAny` back into the 116-byte native image its fields are a transcription of (`Family` at 0, `Addr.Data` covering 2..15, `Pad` covering 16..115) and hands that to `readNativeSockaddr`, the same decode `Getsockname`/`Getpeername` already route through. Two things force the flatten rather than a field-by-field read: the auto body's `Reinterpret<RawSockaddrAny, RawSockaddrInet4>` cannot alias one reference-bearing struct as another (their managed layouts share no offsets at all), and a `sockaddr_in6`'s 16-byte address spans offsets 8..23 — `Addr.Data[6..13]` **and** `Pad[0..7]` — so a decode written against the managed fields has to know that boundary anyway. Reusing the one native decode keeps that knowledge in a single place. (Guarded by four new lines in `SockaddrRoundTrip` driving the method directly on hand-built `RawSockaddrAny` values: IPv4, an IPv6 case whose address deliberately crosses the `Data`/`Pad` boundary, an AF_UNIX name, and an unknown family that must answer `EAFNOSUPPORT`. The Go side writes those fields BY NATIVE OFFSET, so the test states the layout it depends on instead of assuming it.)

**⚠ A hand-own removes its body's EMISSION, and with it any `[assembly: GoImplement]` its body witnessed — check before listing a function in `manualConversionFuncs`.** This is a general trap the sockaddr work surfaced, not a sockaddr detail. Every `GoImplement` record the converter writes comes from a CAST it converted, so a function whose body holds the only casts of a type to an interface is also the only thing recording that pair. `RawSockaddrAny.Sockaddr` is exactly that function for all three `Sockaddr` types, and hand-owning it dropped the three `GoImplement<…, ΔSockaddr>(Pointer = true)` records from `syscall`'s `package_info.cs`. The consequence is not a build failure, which is what makes it dangerous: a MEASURED reconvert of `net` against the shortened `package_info` showed `net` quietly minting its own `syscall_SockaddrInet4жΔSockaddr` adapters instead of using `syscall`'s — the SECOND-IDENTITY regression `samePackageImplements.go` exists to prevent, where reflect and fmt see the wrapper in place of the value's own type and a direct-boxed value compares unequal to an adapter-wrapped one. Declaring the records in the `*_impl.cs` does NOT fix it: a dependent package's converter run reads `package_info.cs`, not the manual file. Recording them from the method set is the real answer, and it has since LANDED — `recordSamePackageImplements` now covers the POINTER method set as well as the value one, so these three pairs are recorded from `types.Implements(*T, Sockaddr)` and no longer depend on any body being converted. Proven by re-running exactly the probe that measured the regression: with `RawSockaddrAny.Sockaddr` suppressed through `manualConversionFuncs`, `syscall`'s `package_info.cs` still carries all three `(Pointer = true)` records and a reconvert of `net` still references `syscall.SockaddrInet4жΔSockaddr` rather than minting its own. **The general trap is narrower now but not gone**, and the check is unchanged for the cases the recorder's gates exclude — a pair whose interface or target is UNEXPORTED, a named FUNC target, a generic, or a promotion deeper than one embed hop is still witnessed only by its casts, as is any pair whose interface is declared in a DIFFERENT package (the recorder is same-package only, so a body holding the only `*T → io.Reader` cast is exactly as load-bearing as before). The cheap check before hand-owning anything: grep the body for interface conversions, and if it has any, reconvert one DEPENDENT package and diff.

**The wall BEHIND this one (recorded 2026-08-11 so it is not rediscovered).** Fixing the sockaddr seam does not by itself make `net` work, and the board's expectation that it would was formed while the panic masked what follows. With `bind` succeeding, `net.Listen` walks on to `internal/poll`'s `pollDesc.init` and stops at `runtime_pollServerInit` — one of ten bodyless `//go:linkname` netpoll entry points that the `PartialStubGenerator` emits as "external (assembly or cgo) function is not implemented". The counterpart exists in the converted runtime (`runtime/netpoll.cs` carries `poll_runtime_pollServerInit`), but nothing wires the linkname across assemblies — and wiring it would not be enough either, because the Windows implementation bottoms out in `netpollinit` → `stdcall2(_CreateIoCompletionPort)` → `asmstdcall`, itself a stub. So this is a second, independent seam whose honest remedy is the managed-API-boundary pattern already used for `sync`'s Mutex and `runtime`'s traceback surface — hand-own the ten `runtime_poll*` contracts against .NET's own completion-port machinery rather than emulating Go's poller — and it is a design arc, not a wrapper repair. **RESOLVED for the listener lifecycle 2026-08-13** (design ruled, arc S1): the ten contracts are hand-owned in `internal/poll/windows/runtime_netpoll_impl.cs` and `net.Listen` now completes against a real kernel — see *The managed netpoller* below. The prediction in this paragraph held exactly, including that the deep wall is the scheduler rather than `asmstdcall`; what it did NOT anticipate is that making `pollWait` wake up is only half the arc, because the overlapped submissions `execIO` issues cannot yet reach the kernel safely (the OVERLAPPED lifetime seam, S2).

**The same fork one step further out: REINTERPRETING an OS byte buffer as a Go struct (`os.readReparseLink`, 2026-08-02).** The entries above are about a struct handed TO the kernel; this is the mirror — a byte slice the kernel FILLED, reinterpreted as a Go struct whose trailing inline array stands in for variable-length data written after it. `internal/syscall/windows`'s `SymbolicLinkReparseBuffer` / `MountPointReparseBuffer` both end in `PathBuffer [1]uint16`, and `Path()` reads the real name through `(*[0xffff]uint16)(unsafe.Pointer(&rb.PathBuffer[0]))[n1:n2]`. In the conversion that field is a golib `array<uint16>` — an 8-byte MANAGED REFERENCE where the OS wrote 2+ bytes of inline UTF-16 — so golib's `PointerExtensions.Reinterpret` correctly declines to alias managed storage for a reference-bearing struct and falls back to the raw-address route; `&rb.PathBuffer[0]` then resolves an object reference synthesized out of path bytes and faults with an `ACCESS_VIOLATION` inside `array<uint16>.get_Item`. That KILLED the C# test host mid-run at `os`'s `TestReadlink`, at test 50 of 178, which is the same silent under-reporting shape `findFirstFile1` produced for `path/filepath`. No converter or golib change can rescue it — a managed array reference can never be laid out like an inline OS array — so it takes the `dir_windows_impl.cs` remedy rather than the blittable-mirror one: `manualConversionFuncs` turns `os.readReparseLink` into a placeholder and `src/core/os/file_windows_impl.cs` decodes the record straight out of the byte slice at its documented offsets (`REPARSE_DATA_BUFFER` header 8 bytes; then `SubstituteNameOffset`/`Length` at +0/+2, `PathBuffer` at +12 for the symlink shape and +8 for the mount point), with every read an ordinary bounds-checked slice index — no pointer, no pinning, no `unsafe` block. `openSymlink` and `normaliseLinkPath` stay auto: they pass scalars, handles and strings. ⚠ `syscall.Readlink` carries the identical defect over its own private `reparseDataBuffer` / `symbolicLinkReparseBuffer` / `mountPointReparseBuffer` copies; it is LATENT (nothing in the validated corpus reaches it) and is recorded rather than fixed speculatively, per the do-it-when-a-suite-reaches-it rule above.

**Atomic pointer ops on a MANAGED pointer field read/write the reference, not a `uintptr`.** The lock-free-cache idiom `atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&x.field)))` / `atomic.StorePointer(…, unsafe.Pointer(v))` — where `x.field` has type `*T` and so holds a `ж<T>` reference — cannot go through the literal conversion: `new @unsafe.Pointer(v)` round-trips the managed reference through its (transient) address, and `(ж<@unsafe.Pointer>)(uintptr)(FromRef(ref …field))` dereferences raw memory, losing GC identity (it NRE'd on the very first read — x/sys/windows's `LazyDLL`/`LazyProc` proc caches at package-init). `convCallExpr.managedAtomicPointerIdiom` recognizes the idiom (the callee is `sync/atomic.LoadPointer`/`StorePointer` and the argument is `(*unsafe.Pointer)(unsafe.Pointer(&Z))` with `Z` of pointer type) and emits golib's managed-referent overloads on the **field box** directly: `atomic.LoadPointer(Ꮡx.of(T.Ꮡfield))` → `ж<ж<T>>` → `Volatile.Read` returning `ж<T>`, and `atomic.StorePointer(Ꮡx.of(T.Ꮡfield), v)` → `Volatile.Write` of the plain `ж<T>` (the stored value unwrapped from its `unsafe.Pointer(…)` conversion). The overloads are additive — a `ж<ж<T>>` argument never matches the existing `ж<@unsafe.Pointer>` (`= ж<Pointer>`) signature, so ordinary `unsafe.Pointer` atomics are untouched. The load stays `unsafe.Pointer`-typed to Go, so a caller's `== nil` still renders `(uintptr)… == nil`; the `ж<T> → uintptr` operator (above) yields 0 for a nil box, so the nil test is correct with no change to the surrounding emission. Blast radius is only the packages using the idiom (x/sys/windows and a handful of stdlib sites), each a pure re-shaping to the managed overload; CNR byte-identical across the behavioral corpus. (Guarded by the `ManagedAtomicPointer` behavioral **output** test — a `*proc`-field lock-free cache initialized once and re-read, vs Go; it NRE'd before the fix.)

The `ref` the helper takes depends on how the pointer argument **renders**. A genuine box — an address-of expression, a local pointer variable, a pointer field, a call result — is the `ж<T>` object, so the ref goes through its boxed value: `FromRef(ref (box).Value)`. But a **deref-aliased** pointer — a pointer *parameter* or pointer *receiver*, which the body renders as the pointed-to value alias (`ref var p = ref Ꮡp.Value`) — is not a box; `.Value` on it is `CS1061` (`nuint` has no `Value` — runtime `select.go` `unsafe.Pointer(pc0)` and `heapdump.go` `unsafe.Pointer(pstk)`, both `*uintptr` parameters). The alias is itself a ref-local into the boxed storage, so the converter takes its ref directly: `FromRef(ref p)`. Detection reuses `exprIsDerefAliasedPointer` (the same discriminator the pointer-reinterpret block uses). This also let the `guintptr`/`muintptr` receiver family (`runtime2.go` `(*uintptr)(unsafe.Pointer(gp))` inside `guintptr.cas`) compile — previously `ref (gp).Value` bound the `[GoType]` wrapper's `Value` *property* (CS0206); the CAS it feeds (`atomic.Casuintptr`) is a `partial` asm stub, so the copy-box semantics match the established reinterpret precedent (compile-milestone bar; the faithful managed-referent `ж<T>` model for those types remains a separate effort). (The bare `unsafe.Pointer(p)` pin stays exercised across the stdlib — runtime `select.go`/`heapdump.go`, and `runtime2.go`'s genuine `*guintptr`→`*uintptr` reinterpret here, whose differing element types keep it off the identity path. The `UnsafePointerParamPin` behavioral **output** test now guards the same-type **identity collapse** of the `(*uintptr)(unsafe.Pointer(p))` shape it originally used — see *A SAME-TYPE reinterpret … collapses to the pointer itself* above — where the whole conversion elides to the box; a `(*byte)(unsafe.Pointer(&value))`-style DIFFERENT-type reinterpret still pins through `FromRef`.)

**Returning an `unsafe.Pointer` parameter whole is a plain value return.** The return path boxes a *pointer parameter* returned whole (`return p` → `return Ꮡp` — the value alias cannot bind the pointer result), and the pointer-result check counts the `UnsafePointer` basic as a pointer. But an `unsafe.Pointer` parameter renders as a plain **value** param (`@unsafe.Pointer zero`) with *no* box, so the prefix referenced a nonexistent `Ꮡzero`/`Ꮡv`/`Ꮡfd` (CS0103 — runtime `map.go` `mapaccess1_fat`/`mapaccess2_fat`'s `return zero`, `mem_windows.go`, and `panic.go` `readvarintUnsafe`'s tuple return). The box form now applies only when the returned parameter's own type is a **genuine `*T`** (deref-aliased, so `Ꮡp` exists); an `unsafe.Pointer` param returns as-is. (Guarded by the `UnsafePointerParamPin` extension — the whole-return, tuple-return, and genuine-`*T`-control shapes, values vs Go; cleared 4 runtime CS0103, 63 → 59.)

The **reverse** direction — reinterpreting a raw address *as* a pointer, `(*T)(p)` where `p` is an `unsafe.Pointer` (or `uintptr`) — is the reinterpret pattern referenced above. Its result is the pointer type `ж<T>`. A plain `(ж<T>)p` cast is `CS0030`: because `unsafe.Pointer` is `Pointer : ж<uintptr>`, reaching `ж<T>` needs the two chained user-defined conversions `Pointer → uintptr → ж<T>`, and C# performs at most one user-defined conversion in a cast. The converter routes explicitly through `uintptr` — `(ж<T>)(uintptr)(p)` — which reads the `T` at `p`'s address via golib's `explicit operator ж<T>(uintptr value) => new ж<T>(*(T*)value)` (with `uintptr(Pointer) => Value`, the address the pointer holds). The deref `*((*unsafe.Pointer)(k))` then adds `.Value`: `((ж<@unsafe.Pointer>)(uintptr)(k)).Value` — Go's read of the `unsafe.Pointer` stored at `k`. This is the identical routing the *dereference* path (`(*int)(p)` inside `*(...)`) already used via its `isPointerCast` flag; the fix extends it to the two shapes that did **not** set that flag: a bare call **argument** `atomicwb((*unsafe.Pointer)(ptr), new)` (runtime `atomic_pointer.go`) and an **extra-paren** deref `*((*unsafe.Pointer)(k))` (runtime `map.go`'s indirect key — `convStarExpr`'s dereference branch sees a `ParenExpr`, not the `CallExpr`, so it never marks the cast). Gated to a **pointer-result** conversion whose **argument** is a raw address (`unsafe.Pointer`/`uintptr` basic); the pointer-to-*named*-type value conversion `(*Base)(defPtr)` (below) has a `*T` argument, is handled earlier, and is not affected. Like every reinterpret through the `uintptr` round-trip, the golib operator reads/boxes a **copy** from a `fixed` address, so this is memory-layout-dependent code whose runtime values are **not the contract** — golib's own `map<K,V>` is what actually runs; the converted `runtime/map.go` only needs to compile. (Guarded by the `UnsafePointerReinterpret` behavioral **Compile + Target** test — both the extra-paren deref and the bare-argument shapes; cleared all 21 `unsafe.Pointer → ж<unsafe.Pointer>` CS0030 in `runtime`, 137 → 114.)

**A SAME-TYPE reinterpret `(*T)(unsafe.Pointer(p))` where `p` is already `*T` collapses to the pointer itself.** Converting a `*T` to `unsafe.Pointer` and back to the *same* `*T` is a no-op identity in Go — the language spec makes `(*Builder)(abi.NoEscape(unsafe.Pointer(b)))` exactly `b.addr = b` (strings.Builder's copy-by-value guard; the type's own TODO says to revert it to that once escape analysis improves). The `uintptr` round-trip above is **wrong** for this shape: golib's `ж<T>(uintptr)` DEREFERENCES-and-COPIES, so `b.addr` became a fresh box over a *copy* of the receiver, never reference-equal to it — and the guard's own `b.addr != b` self-check FALSE-PANICKED on the second call to any `strings.Builder` method (a `Grow` then a `WriteString`, or `strings.Join`'s repeated `WriteString`), surfacing as `panic: strings: illegal use of non-zero Builder copied by value` in the converted **fatih/color** `-recurse` sample the moment color was enabled. `convCallExpr.pointerReinterpretIdentitySource` intercepts this exact shape at the top of the conversion path — a `(*T)(…)` whose source, after peeling an optional escape-analysis identity wrapper (`abi.NoEscape` or a package-local `noescape`, matched by name **and** `unsafe.Pointer→unsafe.Pointer` signature), is `unsafe.Pointer(p)` with `p` of the *identical* pointer type `*T` — and emits `p`'s **box** directly (in the `isPointer` context, so a deref-aliased receiver/param renders `Ꮡb`, not its value alias `b`):
```csharp
internal static void copyCheck(this ж<Builder> Ꮡb) {
    ref var b = ref Ꮡb.Value;
    if (b.addr == nil) {
        b.addr = Ꮡb;                       // was (ж<Builder>)(uintptr)(abi.NoEscape((uintptr)@unsafe.Pointer.FromRef(ref b)))
    } else if (b.addr != Ꮡb) {
        throw panic("strings: illegal use of non-zero Builder copied by value");
    }
}
```
This preserves pointer identity AND shared storage (a write through the reinterpreted pointer now flows back, unlike the copy). A **different** element type is a genuine reinterpret and keeps the `uintptr` round-trip; the interception is `(*T)`-target- and same-element-type-gated (`types.Identical(srcElem, targetElem)`), so it fires ONLY for the identity. Across the 302-package stdlib it rewrites exactly **8** latently-miscompiled sites (`strings.Builder.copyCheck`, `internal/reflectlite`, `internal/syscall/windows/registry`, `os`, `syscall`, and three `runtime` sites) to the cleaner, correct box form — CNR byte-identical everywhere else; the bare `unsafe.Pointer(p)` pin (61 files) and the genuine-reinterpret round-trip (130 files) both remain and stay compile-guarded by the full build. (Guarded by the `PointerReinterpretIdentity` behavioral **output** test — a Builder-style `copyCheck` self-reference called repeatedly must NOT panic, and a genuine copy-by-value MUST still be caught, vs Go; it panicked before the fix — plus the identity-collapse arms of `UnsafePointerParamPin` (param/receiver/field), `PointerSelectorDeref`, and `PointerCastSliceRange`.)

**The identity also collapses when the source pointer is reached DIRECTLY — `*(*T)(p)` / `(*T)(p)` with `p` already `*T`.** This is the same no-op, minus the `unsafe.Pointer` hop: Go's way of re-reading a pointer at a fixed type. It was **not** recognised, and the deref path made it worse than the round-trip above. `convStarExpr`'s casted-pointer-deref branch sets `isPointerCast`, and the conversion renderer took that flag *alone* as licence to emit the raw-address bridge `(ж<T>)(uintptr)(p)`. But `isPointerCast` means only "this conversion is the operand of a deref" — it says nothing about the source being an **address**, and the bridge is only ever correct for one that is. A typed Go pointer is a managed **box**, and a deref-aliased pointer parameter renders as that box's *value alias*, so the `(uintptr)` leg had no conversion at all:

```csharp
// Go:  func derefStruct(p *Pt) Pt { return *(*Pt)(p) }
internal static Pt derefStruct(ж<Pt> Ꮡp) {
    ref var p = ref Ꮡp.Value;
    return ~(ж<Pt>)(uintptr)(p);           // CS0030: cannot convert 'Pt' to 'uintptr'
}
```
```csharp
internal static Pt derefStruct(ж<Pt> Ꮡp) {
    return ~Ꮡp;                            // the box, dereferenced in place
}
```

`pointerReinterpretIdentitySource` now accepts either source form — the direct pointer, or one unwrapped from `unsafe.Pointer(p)` — so the identity is intercepted before the bridge is ever considered. Emitting the box is correct for all three uses at once: a **value read** copies (`~Ꮡp`, plus the array `.Clone()` where the element is an array), an **lvalue write** lands on the real storage (`(Ꮡp).Value = …`) rather than on the round-trip's copy, and **pointer identity** is preserved. Note the recognition must stay pinned to a genuine `(*T)(…)` **conversion** — its `Fun` must denote a type. Matching on argument type alone collapses any one-argument *call* that takes and returns the same pointer type, silently deleting it (`advance(a)` → `a`, `Ꮡp.Swap(Ꮡa)` → `Ꮡa`); CNR caught exactly that across 13 behavioral projects. The corpus-wide A/B footprint is **two lines in one file** — `time.NewTimer`/`AfterFunc`'s `(*Timer)(newTimer(…))`, where `newTimer` already returns `*Timer`, shed a redundant identity cast — because the CS0030 shape needs a pointer *parameter*, which the stdlib's own reinterprets never use; the defect bites converted end-user code and behavioral guards. (Guarded by the `TypedPointerCastDeref` behavioral **output** test — struct, named-numeric, via-`unsafe`, non-deref, lvalue, and local-pointer shapes, plus the one-argument-call over-match control — and by the strengthened `ArrayCastDerefClone`; both verified to FAIL with the fix neutered, with that exact CS0030.)

**Still routed through the bridge (a known gap):** a typed-pointer source whose element type *differs* but shares an underlying — Go permits `(*T)(p)` there — is only partly covered by the named↔named / named↔basic / named↔array re-box routes below. A tag-differing struct pair (`types.Identical` counts tags, Go's conversion rule does not), an unnamed-array ↔ named-array pair, and a named ↔ unnamed struct pair all fall through to the raw-address bridge and mis-render. None occurs in the stdlib corpus, and narrowing the bridge gate without a correct box→box route for them merely trades one broken form (`(ж<Row>)(uintptr)(p)`) for another (`(ж<Row>)p`), so the gate is left as-is and the shapes are recorded here.

A deref whose **starred inner is a func type** (or any non-identifier type) — `*(*func())(add(…))`, runtime `panic.go`'s deferred-slot read `return *(*func())(add(p.slotsPtr, i*…)), true` — misses the identifier-gated cast-deref branch and falls to the default deref path, which must **wrap the cast before `.Value`**: C# postfix binds tighter than a cast, so a naked `.Value` re-binds onto the cast's *inner* operand (`(ж<Action>)(uintptr)(add(…)).Value` reads the inner `@unsafe.Pointer`'s `uintptr` — CS0029 `ж<Action>`→`Action` in the tuple return). The default deref now wraps any type-conversion operand: `(((ж<Action>)(uintptr)(add(…))).Value, true)`. This is the fourth instance of the cast-precedence/extra-paren family, and **indexing** a reinterpret result directly is the fifth: `(*[2]uint64)(x)[0] = 0` (runtime `malloc.go`) appended the pointer-to-array auto-deref `.Value` and the index to the cast render — `(ж<array<uint64>>)(uintptr)(x).Value[0]` read the inner `@unsafe.Pointer`'s `uintptr` and indexed a `nuint` (CS0021); the index emission now wraps a type-conversion base the same way: `((ж<array<uint64>>)(uintptr)(x)).Value[0]`. (Guarded by the `UnsafePointerReinterpret` extensions — the func-type deref in a tuple return and the indexed reinterpret write/read.)

The unsafe builtins `unsafe.Add`, `unsafe.Slice`, and `unsafe.String` accept a length/offset of **any integer type** (Go's `IntegerType` constraint, which includes `uintptr`/`uint`). golib's implementations therefore take a generic `IBinaryInteger` length, truncated to the `int` offset — so `unsafe.Slice(p, uintptrLen)` binds without an explicit cast (a plain `nint` parameter rejected a `uintptr`/`uint` argument with CS1503). (Guarded by `UnsafeBuiltinIntegerLen`.)

Passing an `unsafe.Pointer` **argument to an `unsafe.Pointer` parameter** keeps the `@unsafe.Pointer` struct value — `add(p, x)`, not `add(p.Value, x)`. The struct is an exact match for the parameter. (Guarded by `UnsafePointerArgPassing`.)

**Array-backed defined types reinterpret through storage-sharing `Value` refs, not value copies.** The fiat field-arithmetic shape (crypto/internal/edwards25519 `scalar.go`) reinterprets `&s.s` (a `fiatScalarMontgomeryDomainFieldElement`, written directly over `[4]uint64`) as `(*[4]uint64)` — and as its *sibling* `(*fiatScalarNonMontgomeryDomainFieldElement)` — then **writes element-wise through the reinterpreted pointer** (`fiatScalarFromBytes` parses INTO `&s.s` on a virgin receiver). Neither the copy-boxing named↔named route (each `[GoType("[N]elem")]` wrapper converts only to `array<E>`; a sibling cast needs two chained user conversions — CS0030) nor a plain `ж<>` cast (distinct instantiations) works, and any copy-based route would materialize the wrapper's **lazy** backing on a temp and orphan every write. The emission derefs through the ref-returning `ж<T>.Value` and invokes the wrapper's `Value` property in place — `Ꮡ((Ꮡs.of(Scalar.Ꮡs)).Value.Value)` (underlying-array form) / `Ꮡ((nonMont)((…).Value.Value))` (sibling form, one implicit conversion from `array<E>`) — materializing the backing on the ORIGINAL storage and boxing an `array<E>` struct that shares its `T[]`: element reads and writes flow through. Gating consults the type's **written RHS** (a new per-package pre-pass records each `TypeSpec`'s declared right-hand side, which `Named.Underlying()`'s full resolution loses): only types written *directly* over an unnamed array take this route, so chain-defined view wrappers (`type pallocBits pageBits`) keep the existing copy-box route byte-identically; the same written-RHS gate lets `isTypeConversion` claim the pointer-to-type-literal target `(*[4]uint64)(…)` (no `types.Object` exists for a composite type) without disturbing the pointer-cast slice form (`(*[1<<20]Method)(p)[:n:n]`, internal/abi). Caveat (documented, no stdlib site): a *whole-value* write through the reinterpreted box (`*p = q`) rebinds only the boxed struct. (Guarded by the `NamedArrayWrapper` extensions — a virgin-field write through the underlying reinterpret, a sibling reinterpret aliasing the same storage read-during-write, and a heap-boxed local, all output-compared vs Go.)

**The `uintptr → ж<T>` raw-address reinterpret operator is `explicit` by design.** It boxes a **copy** of the value read at an arbitrary address (the runtime-unsafe reinterpret seam) — never something to happen silently, and every converter-emitted reinterpret already uses explicit cast syntax (`(ж<T>)(uintptr)(p)`). As an *implicit* conversion it also poisoned overload resolution: a `uintptr` argument converted to **both** an `@unsafe.Pointer` parameter (via the numeric `uintptr ↔ Pointer` operators, which stay implicit) and any `ж<T>` parameter, so a **free function and a same-named pointer-receiver method** — runtime's `func add(p unsafe.Pointer, x uintptr)` (stubs.go) vs `func (p *notInHeap) add(bytes uintptr)` (malloc.go), both emitted as static `add` overloads in the package class — were ambiguous (CS0121) at every free-call site whose argument is a **pin of a boxless receiver**: inside a `[GoRecv] ref` method, `unsafe.Pointer(b)` emits the `uintptr`-typed `(uintptr)@unsafe.Pointer.FromRef(ref b)` (runtime `map.go` `b.keys()`/`b.overflow()`/`b.setoverflow()`, `mprof.go`'s stack-record walkers — 6 sites). With the operator explicit, the `uintptr` argument binds only the `@unsafe.Pointer` overload. The reverse `ж<T> → uintptr` (box → address) operator remains implicit — producing a number is not a silent deref. (Guarded by the `FuncVsMethodOverload` behavioral **output** test — the free `add` + direct-ж method `add` overload pair with the boxless-receiver pin call shape, plus both method-call forms, values vs Go; cleared all 6 runtime CS0121, 59 → 53.)

**A cross-package type reference emits its `using <alias> = <namespace>;` even when the file did not import the package under a usable name.** A foreign type renders in short-alias form — `pkg.Type` (`time.Duration`, `abi.Kind`) for a named type, `@unsafe.Pointer` for the `unsafe.Pointer` basic — which resolves only through a file-local alias (`using time = time_package;`, `using @unsafe = unsafe_package;`). That alias is normally generated from a *canonical* (unaliased) `import`, but a file can reference a foreign type with no such import through three routes: **type inference** — a *same-package* function returns a foreign type, so the caller infers a local of that type but never writes `pkg.` and need not import the package (runtime `preempt.go`: `fd := funcdata(f, i)`, where `funcdata` returns `unsafe.Pointer`); a **blank import** (`_ "pkg"`, side-effects-only — **no `using` is emitted for it at all**: the old `using _ = <ns>;` emission hijacked C#'s `_` DISCARD for the whole file, so a deconstruction discard (`(w, _) = w.ensure(…)`, runtime `tracetime.go`) bound the namespace alias instead (CS0118 + CS0029); the import is recorded as a comment, and a genuine type reference still gets its canonical alias from this machinery — e.g. `symtabinl.go`'s `_ "unsafe"` for `//go:linkname`); or an **aliased import** (`import u "unsafe"`, whose alias `u` differs from the canonical `pkg.Name()` prefix the type reference uses). All previously yielded CS0246. The converter now walks every emitted type (`collectTypePackages`, called from `getAliasQualifiedTypeName` — named types by `pkg.Path()`, an `unsafe.Pointer` basic by the pseudo-path `"unsafe"`, recursing through pointer/slice/array/map/chan/generic/func-signature so a `[]time.Duration` element registers too) and, at file close (`visitFile`), supplies the canonical `using <alias> = <namespace>;` for every referenced foreign package the file did not already import canonically. It is idempotent-safe — a canonical import records its path in `canonicalAliasImported`, so `visitFile` never re-emits (duplicates) it — and a non-canonical alias (`using u = unsafe_package;`) coexists with the added canonical one without conflict. It is also **collision-guarded**: the synthesized `using <alias> = <namespace>;` is skipped when its canonical `<alias>` was already bound to a *different* namespace by a real import — cryptobyte's `asn1.go` imports both `encoding_asn1 "encoding/asn1"` (referenced by type, so it reaches this loop) and the subpackage `.../cryptobyte/asn1` (unaliased → alias `asn1`), so synthesizing `using asn1 = encoding.asn1_package` would duplicate the subpackage's `using asn1` (CS1537). The real imports' emitted aliases are tracked per file (`importAliasesEmitted`); the parent stays reachable through its `encoding_asn1` alias, so skipping the canonical one is safe (a non-colliding canonical alias is still supplied — no churn). (The *separate* defect that the type reference itself renders `asn1.ObjectIdentifier` rather than the file's `encoding_asn1.ObjectIdentifier` — `getAliasQualifiedTypeName` uses the canonical alias, not the file's non-canonical one — is tracked independently.) This is the *type-reference* analog of the method-call `addMethodPackageNamespaceUsing`. (Guarded by `UnsafePointerInferredNoImport` — the `unsafe.Pointer` basic arm, scalar/composite/blank-import variants — and `InferredForeignTypeNoImport` — the generic named arm, an inferred `*strings.Reader` in an `fmt`-only consumer.)

**That supplied alias must carry the collision rename.** When the referenced package's using alias is `Δ`-renamed because a same-named CHILD namespace is visible from the import closure (`go.sync`, contributed by `sync/atomic`; `go.unicode`, by `unicode/utf8` — the same CS0576 collision that renames a *canonical* import's alias, above), `getAliasedTypeName` already renders the short-form type reference through the renamed qualifier (`Δsync.Mutex`, `Δunicode.Range16`). The `visitFile` supply loop, however, composed the alias from `packageUsingAlias` alone — the bare, unrenamed name — so it emitted `using sync = sync_package;` (or `using unicode = unicode_package;`) while the reference read `Δsync.Mutex`: the alias binds nothing (CS0246), and the bare alias would itself collide with the child namespace (CS0576). The supplied alias is now routed through `importQualifier` (`getSanitizedImport(importQualifier(alias))`, the same rename every canonical import applies), so the emitted `using Δsync = sync_package;` matches the reference. `importQualifier` is a no-op for any package whose alias is not renamed, so a non-colliding supplied alias stays byte-identical. The trigger is a file that reaches a renamed package's type through the supply route rather than a canonical import — overwhelmingly a **dot import** (`. "sync"` / `. "unicode"`), where the dot brings names in via `using static` yet the converter still qualifies the type, and no canonical `using <pkg> = …` is emitted to carry the rename. Production stdlib code essentially never dot-imports, so the defect stayed latent as an *unused* supplied alias (reflect's `value.go`/`makefunc.go` inferred `sync` without importing it — the `using sync` alias was never referenced, so the wrong spelling compiled); it surfaces in an EXTERNAL (`_test`) variant that dot-imports the package under test, which `unicode`'s `letter_test.go` does (`. "unicode"` + qualified `Range16`/`RangeTable`/`CaseRange`). Because every currently-compiling site had the alias *unused*, the change only ever flips an unused alias (compile-neutral) or fixes a broken one — no site that used `Δpkg.` while getting the bare supplied alias could have compiled. (Guarded by the `DotImportRenamedPackage` behavioral test — `. "sync"` with a `*Mutex` type reference forcing the qualified `Δsync.Mutex` position, output-compared vs Go; neutering the fix reproduces the reported `CS0246: 'Δsync' could not be found`.)

**`uintptr(unsafe.Pointer(x))` builds no `Pointer` object — the operand converts straight to `uintptr` (2026-08-03, r39c).** Go's most common syscall idiom converted to `(uintptr)new @unsafe.Pointer(x)`, and that object was provably dead. golib's `Pointer` is a `ж<uintptr>` whose only value-taking constructor takes a `uintptr`, so the operand is ALREADY converted — by `implicit operator uintptr(ж<T>)`, the very operator the enclosing cast would use — before the wrapper exists; the wrapper stores that finished number in its own one-element slot, and the cast reads it straight back out. The round-trip is the identity, exactly: `uintptr(Pointer)` returns `value.IsNull ? 0 : value.Value`, and the constructor marks the box nil precisely when the address is 0 (`base(value, value == 0)`, with `ж<uintptr>`'s value-peeking `IsNull` arm unable to fire for a value-typed pointee). So the wrapper is no longer emitted:

```go
r1, _, e1 := Syscall(procConvertSidToStringSidW.Addr(), 2, uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(&stringSid)), 0)
```
```csharp
var (r1, _, e1) = Syscall(procConvertSidToStringSidW.Addr(), 2, (uintptr)Ꮡsid, (uintptr)ᏑstringSid, 0);
```

**The pin is what makes this safe to elide, and it is not the wrapper's.** Since r38 every managed address handed to native code is pinned for the POINTER's lifetime (*EVERY managed address handed to native code is pinned…*, above) — but `EnsureStableAddress` / `pinnedArrayData` set `m_pin` on the **operand** box, released when the operand is collected. The `Pointer` wrapper owns no pin, holds no reference to the operand, and tracks no lifetime; its `ж<uintptr>` slot holds a number. Eliding it therefore cannot shorten any pin, move any address, or change any value — a strictly-dead allocation, which is why this is a peephole and not a semantic change.

The rule is keyed at the **wrapper's own emission site** (`unsafePointerBoxEmission`, marked per-CallExpr by `markDeadUnsafePointerBox` from the enclosing conversion), so it is self-limiting: the `unsafe.Pointer` arms that render a raw address by other means — `@unsafe.Pointer.FromRef(ref x)` for a deref-aliased pointer receiver, `((@unsafe.Pointer)(uintptr)v)` for a named uintptr/pointer operand, and `(@unsafe.Pointer)default!` for the literal `nil` — never consult the mark and are byte-identical. The enclosing conversion must be one that renders a `(uintptr)` cast around the operand, which is the basic `uintptr(…)` target and the named-over-uintptr target that hops through its underlying (`Handle(…)`, `syscall/security_windows.cs`'s `LocalFree` defers). An operand that binds looser than a cast is parenthesized (`unsafe.Pointer(uintptr(p) + off)`); an address-of operand is not, because `&x` renders as the primary box form `Ꮡx` / `Ꮡ(…)` / `Ꮡs.at<T>(i)`.

Corpus footprint: **494 sites across 45 files** in a seeded two-temp-root A/B whole-stdlib reconvert — 249 in `syscall/zsyscall_windows.cs` alone, 65 in `internal/syscall/windows/zsyscall_windows.cs`, 25 each in `runtime/heapdump.cs` and `runtime/os_windows.cs` — plus 10 across four behavioral goldens; every changed line in the A/B is reproduced exactly by the transformation and none is anything else. Measured with a `GC.GetAllocatedBytesForCurrentThread` probe over 2,000 calls: `syscall.Write` **1,072 → 544 B/op** (−49.3 %) and `os.File.WriteString` **9,208 → 8,680 B/op**, i.e. 528 bytes — three wrapper objects — off every zsyscall wrapper call chain. (The hand-owned `crypto/subtle/xor_generic.cs` keeps its three sites, as it must: the converter regenerates it into the `xor_generic.cs.auto` review sibling instead of over it.) (Guarded by the `UintptrUnsafePointerIdiom` behavioral **output** test — address identity across two takes of one global, a pointer parameter, the nil pointer's 0, an array-element stride, an existing `unsafe.Pointer` value round-trip, a dereferenced pointer-to-pointer operand, and a write read back through the same pointer, all vs `go run` — plus the re-baselined `NilPointerUintptr`, `NilPointerParamUnsafePointer`, `FixedArrayBufferPointer` and `UnsafeOperations` goldens, which diverge the moment the peephole is neutered.)

### A pointer `reflect` handed out as an `unsafe.Pointer` must convert BACK — the order token is remembered, not redefined

A Go pointer to managed storage has no machine address to report, so every projection of one to a scalar answers with a stable **order token** instead: `INilPointer.PointerOrderToken`, whose own remarks say plainly that tokens "are order keys, never an identity substitute". `reflect.Value.Pointer` and `reflect.Value.UnsafePointer` both project through it (`reflect/value_impl.cs`'s `reflectPointerToken`), and that contract is exactly right for the consumers it was written for — `fmt`'s `%p`, and `internal/fmtsort`'s ordering of pointer-keyed map entries, which compares two tokens arithmetically.

It is not sufficient for the other direction Go permits: converting the scalar back to a pointer and dereferencing it. `go/types`' own test suite does precisely that to reach the unexported `Config._Trace` field:

```go
func boolFieldAddr(conf *Config, name string) *bool {
	v := reflect.Indirect(reflect.ValueOf(conf))
	return (*bool)(v.FieldByName(name).Addr().UnsafePointer())
}

*boolFieldAddr(&conf, "_Trace") = manual && testing.Verbose()   // check_test.go:166
```

which converts to

```csharp
internal static ж<bool> boolFieldAddr(ж<types.Config> Ꮡconf, @string name) {
    var v = reflect.Indirect(reflect.ValueOf(Ꮡconf.OrTypedNil()));
    return (ж<bool>)(uintptr)(v.FieldByName(name).Addr().UnsafePointer());
}
```

`ж<T>`'s `explicit operator ж<T>(uintptr)` builds a **native-address** box over whatever number it is handed (`m_nativeAddr`), so the `.Value` store writes a `bool` at the numeric value of an order token — an access violation when that page is not mapped, and silent heap corruption when it is. It killed `go/types`' converted test host outright at the first test that reaches the idiom, `TestCheck/blank.go`, with 542 verdicts behind it; the fault arrives as a bare `System.AccessViolationException` in `testFilesImpl` with no frame below it, because the faulting store is inlined at the call site.

The information needed to do better is never actually lost. `reflect.Value.Addr` surfaces the **real aliasing box** — an addressable Value already carries `addrBox`, minted by `GoReflect.FieldAliasBox` — and only the projection to a scalar discards it. So golib's `ManagedPointerTokens` (`golib/ж.PointerTokens.cs`) remembers the association the projection drops: the token that was handed out, and the box it named. `reflectPointerToken` registers on the way out, and the `uintptr → ж<T>` operator consults the table **first**, so a token that came from there recovers its own box and aliases the original storage exactly as Go's pointer would. Everything else keeps the pre-existing native-address route unchanged.

Two properties are deliberate. **The token VALUE does not change.** The obvious alternative — minting self-identifying handles from a reserved numeric range — would also change what `%p` prints and what order pointer-keyed maps print in, because those read the very same token; carrying the association out of band instead leaves every existing observable byte-identical, and costs one dictionary probe on a conversion that was already allocating an object. **Entries are weak, and verified on resolve**: the table must never be the reason a box stays alive, and a resolve re-derives the box's current token and requires equality, so a stale entry cannot answer. The type-descriptor path (`typeDescriptorOrderToken`, which packs type NAMES so fmtsort orders them lexically) returns before registration and is untouched — those tokens are shared by every descriptor with the same name and are not identities to recover.

Corpus footprint of the idiom: in all of GOROOT it is `go/types`' `check_test.go` (bool and string), its `cmd/compile/internal/types2` twin, and four descriptor-walking sites in `reflect/type.go`; `sync/atomic`'s two uses only test alignment (`p&7 != 0`) and never dereference. (Guarded by the `ReflectFieldAddrWrite` behavioral **output** test — writes to a bool, a string and an int field through `Addr().UnsafePointer()`, each read back through the ORIGINAL struct to prove aliasing rather than a detached copy, two derivations of one field compared for pointer equality, and the untouched neighbours asserted unchanged, all vs `go run`. It faults with an access violation on pre-fix golib.)

### Pointer DISPLAY never dereferences out-of-range; `unsafe.StringData("")` is nil

Printing a pointer (`ж<T>.ToString()` → `PrintPointer`, the stub-fmt fallback for `%v`/`%p` of a
pointer) only needs an address-like `0x…` token, but the printer read `ptr.Value` to derive one —
and an array/slice-ELEMENT reference can legally sit outside its backing store's valid range (the
zero index of an EMPTY pinned buffer, or one-past-the-end pointer arithmetic), where that read
throws `IndexOutOfRangeException` and kills the host (strings' `TestClone`, Phase-4 row R9).
`PrintPointer` now checks an element reference's index against its backing store first and prints
the BACKING STORE's identity when the element is unreadable — stable per pointer box, never a
throw. Relatedly, `unsafe.StringData` of an EMPTY string now returns **nil**: Go documents the
empty-string result as unspecified-may-be-nil, its runtime returns nil (probed — so distinct empty
strings' data pointers compare EQUAL, which `TestClone` asserts), and golib's
pin-a-fresh-buffer-per-call implementation could never satisfy that identity. Addresses differ run
to run, so behavioral coverage checks printed SHAPE and nil-identity (`UnsafePointerPrint`); the
out-of-range print itself has no Go-parity spelling from converted code today (the
`unsafe.Add`-through-`unsafe.Pointer` seam loses the element box), so that property is guarded at
the golib level by `GolibTests.PointerPrintTests` — a golib UNIT-test project (beside
`ChannelTests` under `/tests/library/`) for runtime properties no Go↔C# output comparison can
reach.

**`unsafe.String(ptr, 0)` reads nothing, so it must not pin `ptr` either.** That same empty-buffer
element reference reaches the string builtin, by a route the standard library takes constantly on
Windows: `syscall.UTF16ToString` truncates its argument at the first NUL and returns
`unsafe.String(unsafe.SliceData(buf), len(buf))`, so an **all-NUL** `[N]uint16` — every unset
`WCHAR` field of a Win32 record (`WIN32_FIND_DATAW.cAlternateFileName` on a volume with 8.3 name
generation disabled, `MIB_IFROW.wszName`, `PROCESSENTRY32.szExeFile`, `STARTUPINFO.lpDesktop`) —
arrives as a zero-length `buf`. `SliceData` is documented to return a **non-nil** pointer to an
unspecified address for a non-nil slice of capacity 0 (only a *nil* slice yields nil), which this
model materializes as an index-0 box into a zero-length backing array; `unsafe.String` then pinned
that referent (`fixed (byte* p = &ptr.Value)`) to build the string and threw
`IndexOutOfRangeException` where Go returns `""`. The zero-length case now returns the empty string
**before** the pointer is touched, which is precisely Go's rule: a run-time panic occurs only when
`ptr` is nil *and* `len` is not zero, so a length of zero dereferences nothing whatever the pointer
is. `SliceData` is deliberately left alone — Go specifies the non-nil-pointer-to-unspecified-address
result for `cap == 0`, so returning nil there to dodge the deref would trade a throw for a wrong
answer. `unsafe.Slice(ptr, 0)` needs no matching guard: its element-window route
(`TryGetElementWindow`) already yields an empty aliasing window over a zero-length backing without a
deref. (Guarded by the `UnsafeStringEmpty` behavioral **output** test — the all-NUL `[14]`/`[260]`
buffers, terminated/unterminated/leading-NUL controls that keep truncation honest, and the
`SliceData`/`StringData` zero-length matrix with non-zero-length positive controls, compared vs
`go run`; verified to throw with the guard removed.)

**`unsafe.SliceData` is an INTERIOR POINTER, not a pin.** Go defines it as `&slice[:1][0]`, so the
faithful model is the array-element reference `Ꮡ(s, 0)` — the exact box the converter emits for
`&s[0]`. It was instead a pinned-buffer box over `slice.buffer`, and that was wrong three ways at
once:

| The pin | The consequence |
|:--|:--|
| `GCHandle.Alloc(…, Pinned)` refuses storage whose element type carries a managed reference | `SliceData` over **any** such slice threw `ArgumentException: Object contains references`. `log/slog`'s `GroupValue` is the corpus witness — `groupptr(unsafe.SliceData(as))` over `[]Attr`, rebuilt by `unsafe.Slice` in `Value.group()` — and it infrastructure-errored every grouping path in the package (5 `testing/slogtest` rows). |
| The pin covered the whole backing array from index 0, ignoring the slice's LOW bound | `SliceData(s[2:])` addressed element 0 rather than element 2, and did not compare equal to `&s[2]` as Go's pointer identity requires. |
| `PinnedBuffer` implements `IArray<byte>` alone | `ж<T>.Value`'s `array is IArray<T>` test failed for every element type but `byte`, so the derived pointer was undereferenceable — `InvalidOperationException` instead of the element. |

Pinning was never what `SliceData` *means*. An address is needed only when the pointer is converted
to `uintptr`/`void*`, and `ж` already pins on demand at exactly those conversions
(`EnsureStableAddress`), declining gracefully for storage that cannot be held still. The element
reference additionally makes the round trip **alias** rather than snapshot — `unsafe.Slice`'s
`TryGetElementWindow` arm rebuilds a window over the original backing, so a write through the
rebuilt slice reaches the source, which is Go's semantics. `StringData` keeps its pin: a `@string`
is a `byte[]`, always pinnable, and its emptiness identity is the property `TestClone` asserts.
(Guarded by the `UnsafeSliceDataAliasing` behavioral **output** test — a `[]struct{string;int}`
round-tripped through `SliceData`/`Slice` and written through, `SliceData(s) == &s[0]` identity, a
re-sliced source's low bound, an `[]int64` and a `[]*T` deref, and the plain `[]byte` control —
plus `UnsafeStringEmpty`, which pins the zero-length behavior above unchanged.)

### Reinterpreting a pointer to a defined type with identical underlying — `(*Base)(p)`
A Go conversion `(*Base)(p)` where `p` is a `*Def` and `Base`/`Def` share an *identical underlying* type (one is a defined type over the other, e.g. `type pinnerBits gcBits`, or both over the same type) reinterprets the pointer. C# has no conversion between the two distinct generic instantiations `ж<Def>` and `ж<Base>`; only the `[GoType]` wrapper's **value** conversion `Def ↔ Base` exists. So the converter performs the reinterpret on the value and re-boxes it:
```go
func (s *mspan) newPinnerBits() *pinnerBits { return (*pinnerBits)(newMarkBits(s.nelems * 2)) }   // newMarkBits returns *gcBits
```
```csharp
internal static ж<pinnerBits> newPinnerBits(this ref mspan s) {
    return Ꮡ((pinnerBits)(~newMarkBits(((uintptr)s.nelems) * 2)));   // deref the ж<gcBits> box, value-convert, re-box
}
```
The argument is **dereferenced first** (`~box`) when it renders as a genuine pointer box — a call result, a local box, or a pointer field — because the value conversion operates on the underlying value, not on `ж<Def>` (a plain `(pinnerBits)(ж<gcBits>)` is `CS0030`). A deref-aliased pointer **parameter/receiver** already renders as the pointed-to value (`Δp`, not a box), so it value-converts directly with no `~` — the original `(*atomic.Uint32)(p)` receiver case (runtime/mprof `goroutineProfileStateHolder`). Both forms box a **copy** (`Ꮡ`): the shared underlying is the wrapped value, and a defined-over-struct wrapper holds it in a `readonly` field, so there is no write-through to lose; this matches the long-standing copy semantics of this branch (the runtime intrinsics behind these are assembly stubs). Both ships stay in managed `ж<>` land — no raw-address round-trip. (Guarded by `NamedPointerReinterpret`.)

The **third direction** — a pointer to a BASIC type reinterpreted to a defined type over that
basic — takes the same value-convert-and-re-box route: fmt's `(*stringReader)(&str)` (`type
stringReader string`) emits `Ꮡ((stringReader)(str))` — the address-of collapses with the value
deref, restricted to this arm so the long-guarded emissions stay byte-identical. Writes through
the box hit the copy, which is faithful for the pattern (the source string is never re-read).
Guarded by `NamedPointerReinterpret` (`tail`/`consume`). The **deferring-receiver rule** is a
sibling of these box-form decisions: a method that defers or recovers at FUNCTION level and also
references its receiver takes the direct-ж receiver (`this ж<T> Ꮡx`) rather than `this ref T`,
whose deref alias then emits inside the frame's `try` (`bodyWrappedInDeferContext`; fmt `ss.Token`,
guarded by `DeferCallOrder` `acc.add`). The direct-ж form is the alloc-free, race-free one, and it
is also what a deferred closure needs, since a lambda cannot capture a `ref` local.

The same block also covers a **named-numeric pointer reinterpreted to its underlying *basic* type** — `(*uint64)(head)` where `head` is a `*lfstack` (`type lfstack uint64`). This is the runtime's atomic-on-a-named-integer pattern: `atomic.Load64((*uint64)(head))` / `atomic.Cas64((*uint64)(head), …)` on the named atomic types **`lfstack`** (uint64, `lfstack.go`), **`sweepClass`** (uint32, `mgcsweep.go`), **`profAtomic`** (uint64, `profbuf.go`), and **`sysMemStat`** (uint64, `mstats.go`). `ж<lfstack>` and `ж<uint64>` are distinct generic instantiations with no conversion (`CS0030`); the reinterpret condition is generalized from *Named↔Named* to also fire when the **result** elem is a **basic** type whose underlying equals a **named** argument elem's (`namedToBasic`), and again for the reverse (`basicToNamed`).

#### These three arms now ALIAS instead of boxing a copy (2026-08-03, r38-gob)

Everything above described the emission as *value-convert-and-re-box*, and justified the copy each time
the shape came up — "no write-through to lose", "the intrinsics are asm stubs", "the source string is
never re-read". **That justification was a property of the call sites the arm happened to have, not of
the conversion**, and Go's `(*U)(p)` says the opposite: the derived pointer names **p's own storage**, so
a write through it is visible through `p`. `encoding/gob` is where the difference stopped being
theoretical. `Gobber.GobDecode` decodes straight back through a reinterpret used as a **call argument** —

```go
type Gobber int
func (g *Gobber) GobDecode(data []byte) error {
	_, err := fmt.Sscanf(string(data), "VALUE=%d", (*int)(g))   // writes THROUGH the reinterpret
	return err
}
```

— which is neither a deref context nor a raw-address source, so it took this arm and emitted
`fmt.Sscanf(…, Ꮡ((nint)(g)))`: `Sscanf`'s write landed in a throwaway box, `g` never changed, and the
decoder returned 0 for 23. All three arms now route through golib's **aliasing** reinterpret —
`reinterpretManagedEmission`, the same emission this file already produced for the identical conversion
in a deref or raw-address context — so the arm's own gate is the only thing that changed:

```csharp
public static error GobDecode(this ж<Gobber> Ꮡg, slice<byte> data) {
    var (_, err) = fmt.Sscanf(((@string)data), "VALUE=%d"u8, Ꮡg.Reinterpret<Gobber, nint>());
    return err;
}
```

`PointerExtensions.Reinterpret` is where the "can the managed model express this alias?" decision already
lives (`ReinterpretAliasesStorage`), so the converter delegates rather than re-deciding; it reports false
for the two shapes that must keep the re-box — an IDENTITY conversion (intercepted upstream by
`pointerReinterpretIdentitySource`) and an **array** pointee, whose lazily-materialized backing store a
storage reinterpret bypasses — so the chain-defined `type pallocBits pageBits` pair the written-RHS gate
deliberately leaves on this arm falls through unchanged. A useful second-order effect: the entry deref
alias these functions carried (`ref var head = ref Ꮡhead.DerefOrNull();`) becomes dead, because the body
now names only the box, so the alias-liveness scan drops it.

**Whole-stdlib A/B (converter-vs-converter, both sides seeded per ritual 1a): 14 files, 41 hunks, every
one this substitution and nothing else** — and the census is what shows the copy was never harmless.
Beyond the runtime's asm-stub atomics (`lfstack`, `sweepClass`, `profAtomic`, `sysMemStat`,
`pinnerBits`), it silently broke real write-through in **`flag`** (every `newBoolValue`/`newIntValue`/…
returns `(*boolValue)(p)`, so a parsed flag never reached the user's variable), **`crypto/tls`**
(`clientShares.ReadUint16((*uint16)(&ks.group))` parsed key-share groups and signature schemes into a
copy), **`crypto/cipher`** (`(*cbcEncrypter)(newCBC(b, iv))`, whose IV mutates per block),
**`image/png`** (`(*encoder)(buffer)` over a pooled `EncoderBuffer`), **`go/types`**
(`(*term)(t)`), and **`crypto/internal/boring/bbig`**. The reconverted corpus builds **304/304, 0
errors**. (Guarded by the extended `NamedNumericPointerReinterpret` behavioral **output** test: the read
path it always covered, plus write-back through an argument-position reinterpret — the gob shape — a
named→named struct reinterpret held in a local and written through a field selector, and a basic→named
`(*namedString)(&s)`; verified to FAIL on stdout with the fix neutered.)

### The club-41 mop-up batch (flag/flate/binary/syntax roots)
Nine coupled rules from the shallow-stack campaign:
- **Named func types implementing interfaces** (flag's `funcValue`): a delegate cannot be a
  partial struct — the generator routes Delegate records to the VALUE adapter
  (`new funcValueᴠValue(v)`), whose Go methods are package extensions binding on the wrapped
  copy; non-struct record kinds SKIP rather than throw (a throw kills the package's entire
  generator run). Guarded by `FirstClassFunctions` (`handler.tag`).
- **Ref receivers never take box renders**: a `[GoRecv] ref` receiver has NO box — the
  escape-heap and lambda-capture convIdent arms fall through to the value alias (flate init's
  `d.fill = (*compressor).fillStore` emitted a nonexistent `Ꮡd`, CS0103). Guarded by
  `FirstClassFunctions` (`worker`).
- **Func-field callees drive argument treatment**: `getFunctionSignature` resolves a
  FUNC-typed field's signature (`d.fill(d, b)` — the receiver arg renders as the box for a
  `ж<T>` slot, CS1503).
- **Integer wrappers carry the UntypedInt bridge** (`(token)(endBlockMarker)` — C# never
  chains two user conversions, CS0030). Guarded by `SortArrayType` (`levelToken`).
- **Package aliases shadowed by method names** qualify through the `_package` class
  (`sort_package.Sort(…)` — flate's `byLiteral.sort` bound the method group, CS0119).
  Guarded by `SortArrayType` (`PeopleByAge.sort`).
  Same-package tests also participate in that shadow set even during an **ordinary production
  conversion**. Under recompile fallback an in-package `_test.go` declaration lands in the SAME C#
  package class and can shadow a production alias; under white-box reference it lives in a bridge,
  but external-test references still need one stable production spelling — yet go/packages'
  production package omits test files. hash/maphash's `smhasher_test.go`
  declares `func (k *bytesKey) bits() int` while `maphash_purego.go` imports `math/bits`, so
  `bits.Mul64(a, b)` binds the method group in the test assembly (CS0119, plus CS8130 on both
  deconstructed results). Every production package conversion therefore performs a cheap,
  build-constraint-aware directory scan of its in-package `_test.go` function/method names and
  folds them into `packageFuncMethodNames`; it loads and type-checks no test dependency graph.
  External-package tests are excluded because they emit into another C# package class. This makes
  production output mode-stable: ordinary and `-tests` conversion both emit
  `math.bits_package.Mul64(a, b)`. When a test-only declaration is specifically responsible, the
  statement also explains the otherwise surprising spelling:

  ```csharp
  // Fully qualified to avoid alias shadowing by the same-package test declaration "bits".
  var (hi, lo) = math.bits_package.Mul64(a, b);
  ```

  This remains a REFERENCE-spelling change only — never a symbol rename, so production names stay
  pinned (see `testMethodRenames`). On entry to `processTestConversion` the sibling list is cleared:
  the in-package variant sees the declaration in its own typed universe, while the external
  variant's declarations live in a different C# class. Guarded by
  `TestSiblingTestDeclaratorsContributeAliasShadow`, including build-excluded and external-test
  negatives plus the exact explanatory comment; hash/maphash's normal-vs-`-tests` production file
  is also byte-identical.
  A **Δ-renamed foreign CONST reached through that fallback** must still substitute the
  renamed member: the composed lookup key (`time_package.Second`) misses the alias map
  (keyed on the plain package name, `time.Second`), so `getAliasedTypeName` retries with
  the `PackageSuffix` stripped and, on a CONST hit, keeps the `_package` qualifier while
  substituting the alias — `time_package.ΔSecond` (crypto/tls's `Config.time` method ×
  time's `Second` const-vs-`Time.Second()` collision; the raw name bound the
  `Second(this Time)` extension method group, CS0019 ×2). Gated to consts: const entries
  exist only for collision-renamed members, while type entries cover every exported type,
  whose raw `_package`-qualified renders already bind. Guarded by
  `ShadowedImportConstLib`/`ShadowedImportConstUser` (the lib Δ-renames `Peak` for its own
  `Meter.Peak` collision; the user's `gauge.ShadowedImportConstLib` method shadows the
  import and `Span(2) * ShadowedImportConstLib.Peak` reaches the renamed const through the
  fallback, output-compared vs Go).
- **Blank params synthesize names when the body discards** (`_ = b[7]` bound the blank
  `littleEndian` receiver, CS0029) — encoding/binary's bounds-check hints. Guarded by
  `TypeSwitch` (`marker.tag`).
- **Defined-over-named-struct composites wrap the underlying** (`decoder{order: o}` →
  `new decoder(new coder(order: o))`, CS1739). Guarded by `NamedPointerReinterpret` (`view{}`).
- **Labeled switches declare their break target** (`break_BigSwitch:;` after the switch —
  both switch visitors now mirror visitForStmt, CS0159). Guarded by `SwitchBreakInCase`
  (`pick`).
- **Short declarations keep the named-numeric cast** (`p := printFlags(0)` re-imposes
  `((printFlags)0)`, CS1503) and **empty-interface switch tags compare via AreEqual**
  (`switch err := recover(); err { case ErrLarge: }`, CS0019).

### Slice-to-array: the VALUE form copies, the POINTER form ALIASES
Go has two slice-to-array conversions and they are different conversions, so each gets its own
golib entry. Both panic Go-style on a short slice.
- The Go 1.20 **value** form `[4]byte(slice)` emits `new array<byte>(s, 4)` — the COPY
  constructor `array<T>(slice<T>, nint)`. Go's conversion copies, so this is exactly faithful
  (netip `AddrFromSlice`, CS1955).
- The Go 1.17 **pointer** form `(*[4]byte)(slice)` emits `Ꮡ(array<byte>.Alias(s, 4))` — an
  ALIASING window over the slice's own backing store. The Go spec is explicit that here "the
  slice and array share their underlying array", so the array is a *view*, and `array<T>` carries
  a `(low, length)` window for exactly this one construction; every other construction spans its
  whole backing, where the window is inert.

**This was a copy until 2026-07-31, and the copy was a silent wrong answer, not a performance
detail** — every write through the pointer was discarded. `image/png`'s encoder is the corpus
witness: its `cbTCA8` row loop converts each four-byte destination window and writes the
un-premultiplied pixel through it,

```go
d := (*[4]byte)(dst)
s := (*[4]byte)(src)
…
d[0] = uint8((uint32(s[0]) * m / a) >> 8)
```

so against a copy *every* pixel write went nowhere and any RGBA image that was not fully opaque
encoded as an all-zero image. It surfaced as `TestWriteRGBA`'s "50/50 Transparent/Opaque RGBA"
and "RGBA with variable alpha" subtests, and the two subtests that did pass passed by luck: the
opaque one takes the `cbTC8` path entirely, and the fully-transparent one wants all-zero output,
which is what a lost write also produces.

The window is where the aliasing is, not merely the sharing: the png loop converts `cr[0][1:]`
and re-slices it forward, so the array's element 0 is not element 0 of the backing store. Three
consequences follow and all three are Go's:
- `array<T>.Clone()` — Go's by-value array copy — materializes the *window*, so a copy is a full,
  offset-free array again;
- `p[:]` bounds its capacity at the array's end (`cap` 4, not the rest of the slice);
- `&(*[4]byte)(s)[0] == &s[0]`: `ж<T>.CanonicalElement` resolves an element pointer taken through
  a window to the same absolute element as one taken through the slice.

Guarded by `SliceToArrayPointerAlias` (offset write-through, read-back through the pointer,
value-conversion copy, deref copy, `p[:]` aliasing and `len`/`cap`, element identity, and the
png-shaped windowed loop) and by `NamedPointerReinterpret` (`sliceToArray`). The POINTER-sourced
sibling `(*[N]T)(unsafe.Pointer(p))` is
[below](#an-element-pointer-reinterpreted-as-an-array-pointer-aliases-the-elements-storage).

### An element pointer reinterpreted as an array pointer ALIASES the element's storage
`(*[N]T)(unsafe.Pointer(p))` with `p` a `*T` is the same conversion reached from a POINTER instead
of a slice, and it emits the same kind of window — `array<T>.AliasPointer(p, N)`, which yields the
`ж<array<T>>` directly. It is what Go's stdlib reaches for whenever a `*T` really names a run of
elements: os's `TestReadStdin` fake fills internal/poll's read buffer with
`copy((*[10000]uint16)(unsafe.Pointer(buf))[:n:n], s16)`, and `syscall.Readlink` decodes a reparse
record through `(*[0xffff]uint16)(unsafe.Pointer(&data.PathBuffer[0]))[:n:n]`.

Before this the shape took the raw-address route (`(ж<array<T>>)(uintptr)(…)`), which cannot
express it in either of its two forms. Dereferenced, that box reads an `array<T>` STRUCT — a
backing-store reference plus bounds — out of the pointed-at *data*, i.e. a fabricated managed
reference. Sliced, the `isPointerCast` fusion in `convSliceExpr` catches it first and produces a
`slice<T>` COPY of the memory, so every write through it is discarded: all 462 of `TestReadStdin`'s
subtests read back zeros (`have [0 0 0…] want [abc…]`).

Three rules bound the arm, and each is a real limit of the managed model rather than a convenience:
- **Same element type only.** `array<T>.AliasPointer` windows the pointer's own `T[]` backing; a
  `T[]` view over differently-typed storage does not exist in C#. `(*[2]byte)(unsafe.Pointer(&x.Port))`
  over a `uint16` keeps the address route — that is the raw-metal fork, unchanged.
- **The window is decided at RUNTIME.** Only a pointer that actually addresses managed array/slice
  storage has a backing to window; a heap box, a struct field or a native address falls back to the
  identical address route inside golib, so nothing that worked before changes shape.
- **`N` is CLAMPED to the storage that exists.** Go's `N` in this idiom is a promise, not a length —
  it is spelled `10000` / `1<<16` / `0xffff` over buffers a few elements long and the result is
  always immediately re-sliced to the real count. Honoring it literally would put the window's own
  bounds past its backing store, where a full-slice expression is an exception rather than a window.
  So `len(*p)` reports the addressable extent, not Go's `N`, in the huge-`N` case; an overrun is
  then a Go-style index panic instead of the silent corruption the same overrun is in Go. (Where `N`
  fits — the ordinary `(*[4]byte)(unsafe.Pointer(&s[i]))` — `len` is exactly Go's.)

One latent defect in the same family fell out with it: `SliceExtensions.slice(this array<T>, …)`
sliced the RAW backing store, ignoring `m_low`/`m_length`, so explicit bounds over ANY window —
`Alias`'s as well as `AliasPointer`'s — addressed the source's elements rather than the array's
(`p[1:3]` of a window over `buf[1:]` yielded `buf[1:3]`, not `buf[2:4]`). The Range indexer already
resolved through the window; the explicit-bounds path now does too, which is the path the
`[:n:n]` idiom takes.

Guarded by `ArrayPointerElementAlias` (write-through at element 0 and at an offset, the huge-`N`
`[:n:n]` copy shape, indexed write-through, read-back through the pointer, deref copy, `p[:]` and
`p[1:3]` aliasing with `len`/`cap`, element identity, a Go fixed-array source, and a re-sliced
view).

### A direct-ж method on a value field-chain boxes through the &-machinery
A direct-ж (box-receiver) method called on a field of a plain VALUE param — netip's
`ip.addr.halves()`, where Go auto-addresses `&ip.addr` — routes the receiver through the
&-machinery: `Ꮡ(ip).of(ΔAddr.Ꮡaddr).halves()`. This boxes a COPY, which is faithful because
the enclosing Go value param is itself a copy: writes through the method could only ever reach
the local copy in Go too. (Pointer-rooted chains and indexed elements take their own
long-standing arms; this is the remaining value-rooted case.) Guarded by
`StructPointerPromotionWithInterface` (`rig`/`probeRig`).

### Field address of a collision-renamed heap-boxed local uses the raw box name
A heap box always keeps the RAW Go identifier (`ref var Δslice = ref heap<T>(out var <box>slice)`), so taking the address of a FIELD of a collision-renamed boxed local routes through `boxBaseName` -- the raw-name box, never the Δ-renamed alias (CS0103; reflect `SliceOf`'s `&slice.Type`). This matches the whole-value `&p` form and the renamed receiver/parameter boxes:
```csharp
internal static void bump(ж<nint> Ꮡnp) {
```
Guarded by `CollisionRenamedLocalBox` (`bump(&p.n)` on the renamed local `p`).

### A capture-mode method on a shadow-renamed heap-boxed local uses the rendered box name
A capture-mode method — one that escapes its receiver's address, e.g. `cryptobyte.Builder.AddASN1`, which hands `&b` to a callback — called on a heap-boxed VALUE local routes through the receiver box: `var b Builder; b.AddASN1(…)` → `Ꮡb.AddASN1(…)`. Unlike a deref-aliased pointer *parameter* (whose box keeps the RAW name, `Ꮡp`), a heap-boxed value LOCAL keeps its box under the RENDERED name — an escaping local is `ref var b = ref heap(new T(), out var Ꮡb)`, so when the local is SHADOW-renamed its box takes the renamed name. crypto/x509 `marshalCertificate`'s inner `serialiseConstraints` closure declares `var b cryptobyte.Builder`, renamed `bΔ1` to dodge the enclosing method's own `var b` declared LATER (a C# lambda cannot re-declare an enclosing-scope local, CS0136); its box is `ᏑbΔ1`. Emitting the raw-name box `Ꮡb` there both mis-references the outer `b`'s box (declared later in the method → CS0841/CS0103) and, where a same-named outer box does resolve, calls the method on the wrong operand — go/types `conversions.go` called `x.convertibleTo` on the receiver box `Ꮡx` instead of the inner operand box `ᏑxΔ2`:
```csharp
ref var bΔ1 = ref heap(new cryptobyte.Builder(), out var ᏑbΔ1);
…
ᏑbΔ1.AddASN1(cryptobyte_asn1.SEQUENCE, (ж<cryptobyte.Builder> bΔ2) => { … });   // was Ꮡb (CS0841)
```
The receiver-box render resolves the box base through `boxBaseName` with the lambda capture-remap DISABLED, so it yields: the shadow-rendered *declaring* name (`bΔ1`) for an escaping local; the raw name (`Ꮡp`) for a pointer parameter; and — critically — the *declaring* name for a variable CAPTURED by the closure, not its value-snapshot capture name. A heap-boxed local captured by a closure has its box captured directly (`Ꮡonce` in sync `OnceFunc`'s returned closure), so the capture-remapped `Ꮡonceʗ1` (a non-existent box) must not appear. Guarded by `ShadowedHeapBoxReceiver` (an inner closure's `var b` capture-mode method, shadow-renamed against an outer same-named `var b` declared later).

### Nested dereferences parenthesize before the outer `.Value`
A deref whose operand is ITSELF a deref renders with the prefix `~` form, on which a naked postfix `.Value` mis-binds (postfix beats unary: `~X.Value` is `~(X.Value)`). The outer deref wraps the inner one -- reflect `MapOf`'s `**(**mapType)(unsafe.Pointer(&imap))`:
```csharp
var back = (~(ж<ж<array<int64>>>)(uintptr)(@unsafe.Pointer.FromRef(ref (Ꮡip).Value))).Value;
```
Guarded by `PointerCastSliceRange` (compile-shape).

### Function literals returning `unsafe.Pointer` state their return type
A literal with a single `unsafe.Pointer` result can mix return arms of DIFFERENT C# types (reflect `deepEqual`'s `ptrval`: `(uintptr)v.pointer()` on one arm, the raw `v.ptr` on the other), which defeats C# lambda return-type inference (CS8917). The emitted lambda states its return type explicitly; each arm then converts implicitly through the golib operators:
```csharp
var pick = @unsafe.Pointer (bool u) => {
```
Guarded by `PointerCastSliceRange` (compile-shape).

### Interface-returning literals with distinct arm types state their return type too
The same inference gap hits an interface result whose arms return DIFFERENT concrete types — net ipsock.go's `inetaddr := func(ip IPAddr) Addr` returns three pointer-adapter classes (`TCPAddrжΔAddr` / `UDPAddrжΔAddr` / `IPAddrжΔAddr`), which share only the interface (CS8917). When a single non-empty-interface result's return arms carry two or more distinct types, the lambda states the return type explicitly (`Addr (IPAddr ip) => …`); each arm then converts implicitly. Single-typed literals keep the inferred form (zero churn). (Guarded by the `InterfaceCasting` extension `makeAnimal` — an adapter arm plus a value arm, runtime-verified.)

**And the opposite end of the same gap: arms that are ALL untyped `nil`.** `client := func(*TCPConn)
error { <-serverDone; return nil }` (net `net_test`) renders its only arm as `default!`, which carries
no natural type at all — so where the rule above has too many candidate types, this has none, and it
is the same CS8917. The interface arm now also states the return type when the literal is in
assignment position, has a return, and **every** single-result arm is untyped nil. Drift-free by
construction: an all-`default!` arm set never had an inferable natural type, so every site the rule
touches is a site that did not compile. It is the single-result twin of the multi-result
`!hasFullyTypedArm` rule immediately below, and it stays out of argument/return position for the same
reason that one does — those literals are target-typed by their delegate, so nothing is inferred.
(Guarded by the `FuncLitStringConcatReturn` extension: an all-`nil` literal in both `:=` and `var`
form, plus a mixed-arm control that must KEEP inferring.)

### Multi-value literals with no fully-typed arm state their return type — named results included
The single-result inference gaps above generalize to any MULTI-result literal where EVERY return arm carries a typeless element — `return nil, nil, nil, nil, err` on the error arms and `return dnsNames, ips, emails, uriDomains, nil` on the success arm (crypto/x509 `parseNameConstraintsExtension`'s `getValues := func(subtrees) (dnsNames []string, ips []*net.IPNet, emails, uriDomains []string, err error)`). A C# tuple literal with any untyped element has no natural type, so no arm fixes the lambda's return type and delegate-type inference fails (CS8917). The lambda states its tuple return type explicitly, and each `nil` then takes its target element type:
```csharp
var getValues = (slice<@string> dnsNames, slice<ж<net.IPNet>> ips, slice<@string> emails, slice<@string> uriDomains, error err) (cryptobyte.String subtrees) => { … };
```
NAMED results are now included (they were previously excluded): the trigger — a multi-result literal with a return arm but NO fully-typed arm — is identical whether the results are named or not. A bare `return` (which returns the named results) never matches the result arity, so it neither marks has-return nor a false fully-typed arm; a named literal that DOES have a fully-typed explicit arm keeps inferred typing (no return-type prefix, no churn). Guarded by `NamedResultLambdaInfer` (a five-result named-result closure whose error arms return `nil,nil,err` and success arm `e,o,nil`).

### String-returning literals in assignment position state their return type
A literal with a single Go `string` result can mix return arms of DIFFERENT C# types even though every arm is a Go string: a bare string literal is a `"…"u8` `ReadOnlySpan<byte>`, a literal+variable concat binds golib's `operator +(@string, @string)` (so it is `@string` regardless of u8 suppression), and a call into a hand-written stub can return C# `string` (the baseline `fmt.Sprintf` does). `@string` and `string` convert implicitly in BOTH directions, so a lambda mixing those arms has no unique best common type and its delegate type is not inferable — CS8917 on `pick := func(v any) string {…}` whose `case string:` arm returns `"string:" + t` alongside `fmt.Sprintf` arms. In assignment position (`var pick = …`, where C# must infer the delegate type), the lambda states its return type explicitly and each arm then converts to `@string` in place:
```csharp
var pick = @string (any v) => {
```
Argument/return/composite-element literals are target-typed by their receiving delegate type (no inference to fail — and an explicit return type could only add an identity-match constraint against stub delegate types), so they keep the plain form; the Go `var` declaration form emits an explicit delegate type (`Func<@string, bool, @string> pad = …`) and is likewise immune. Gated to the basic string kind — a named string type would need its own conversions. Guarded by `FuncLitStringConcatReturn` (`:=` literals mixing concat, u8-literal, and stub-`Sprintf` arms — including a type-switch body and a right-side literal concat — plus the `var` form; runtime-verified).

The TUPLE-ELEMENT sibling: in a MULTI-result literal, a bare string literal element is worse than typeless — it is *wrongly* typed. Inside a tuple the literal emits as a bare C# `string` (u8 spans cannot be tuple elements), so an arm with no `nil` and no string variable — internal/fuzz `fuzzOnce`'s `return dur, coverageSnapshot, ""` (`func(entry CorpusEntry) (dur time.Duration, cov []byte, errMsg string)`) — counted as "fully typed" in the multi-result scan above and suppressed the explicit tuple return type, letting inference *succeed with the wrong element type*: the destructured `errMsg` was C# `string`, which has no `!=` against a `"…"u8` span (CS0019 rather than CS8917). A basic-string constant literal element whose declared result element is Go `string` now also marks its arm not-fully-typed, so the same explicit-tuple emission fires:
```csharp
var fuzzOnce = (time.Duration dur, slice<byte> cov, @string errMsg) (CorpusEntry entry) => { … };
```
and each `""` converts to `@string` in place via target typing. Same assignment-position gate; a literal whose string elements are all variables keeps inferred typing (the full-stdlib A/B footprint was exactly internal/fuzz worker.cs plus two latent-identity repairs, internal/coverage/decodecounter `sget` and net ipsock.go `addrErr`, both re-proven green). Guarded by the `FuncLitStringConcatReturn` extensions `fuzzish` (named results, `!= ""` on every destructured element) and `sget` (unnamed `(string, error)`); the pre-fix converter fails them with exactly CS0019 ×4.

The NUMERIC sibling: an untyped numeric constant literal element against a differently-SIZED declared result element is wrongly typed the same way. The literal emits bare, so the arm infers the literal's natural C# type — an INT literal is C# `int`, a FLOAT literal C# `double` — where the Go result is e.g. `int64`: net/http ServeContent's `sizeFunc := func() (int64, error) { …; return 0, errSeeker }` had no `nil`/string-literal element on its error arms, counted "fully typed", and inferred `Func<(int, error errSeeker)>` — rejected at the `serveContent(…, sizeFunc, …)` call because delegate types are invariant (CS1662/CS0029/CS1503, and the leaked `errSeeker` element name rides the inferred tuple). Such an element now also marks its arm not-fully-typed, so the same explicit-tuple emission fires:
```csharp
var sizeFunc = (int64, error) () => { …; return (0, errSeeker); };
```
A declared element the literal's natural type already matches (`int32` for an INT literal, `float64` for a FLOAT literal) infers correctly and stays inferred, and Go `int` (C# `nint`) is deliberately exempt — `return 0, err` against `(int, error)` results is pervasive and green today (the element converts implicitly at every use site), so marking it would churn stdlib-wide for no observed defect, the same reasoning that keeps `lambdaConstReturnCastType` away from signed single results. A SUB-negated literal (`return -1, …`) is unwrapped and marked the same way. (Full-stdlib A/B footprint: net/http fs.cs `sizeFunc` — the target — plus three latent same-shape repairs, internal/coverage/decodecounter `rdu32` ×3 and net/http h2_bundle `allocatePromisedID` (both `(uint32, error)`) and internal/zstd `fetchHuff` (`(uint16, error)`). Guarded by the `FuncLitNumericTupleReturn` behavioral test — the sizeFunc shape and a float64 shape both PASSED to typed function parameters, the `-1` arm, and the int/float64-identical controls that must keep inferred typing; the pre-fix converter fails it with exactly the fs.cs trio CS1662/CS0029/CS1503 ×2.)

That Go-`int` exemption is **narrowed, not absolute**: it holds only while every numeric arm at the declared-`int` position is a literal. When such a position carries BOTH a bare-`0` arm (naturally C# `int`) AND a **non-literal** Go-`int` arm (`i + 1` → C# `nint`) — and the other tuple slots on the non-literal arms are typeless (`default!`), leaving a single naturally-typed literal arm to drive inference — C# infers the delegate's first element as `int`, so the `nint` arm then fails to convert (CS0029/CS1662) and the assignment-inferred delegate is rejected at the invariant use site (CS0407). This is bufio `ExampleScanner_*`'s `onComma := func(…) (advance int, token []byte, err error) { …; return i+1, data[:i], nil; …; return 0, data, ErrFinalToken; }`. The multi-result scan therefore additionally records, per declared-`int` position, whether an int-LITERAL arm and a non-literal `nint`-expression arm both occur; when they do, the explicit `(nint, …)` return type is forced (`(nint advance, slice<byte> token, error err) (…) => …`) and each arm converts in place. The all-literal `return 0, err` shape has no non-literal arm, so it keeps its inferred emission untouched — the corpus is undisturbed (behavioral CNR byte-identical across all 451 projects). (Guarded by the `FuncLitNumericTupleReturn` extension — the `mixedIntArms`/`onComma` shape assigned and passed to a typed parameter, alongside the unchanged all-literal `intControl`; the pre-fix converter fails it with CS0029/CS1662/CS1503.)

### Numeric-returning literals with untyped-constant arms state their return type
The SINGLE-result numeric sibling of the string arm above (2026-07-17; the Phase-4 blocker-map row B7b — strings ×3, bytes ×2): a literal with a declared numeric result whose return arm references a **named untyped constant** — strings/bytes TestMap's `maxRune := func(rune) rune { return unicode.MaxRune }`. The const reference emits as a golib `Untyped*` wrapper reference (`Δunicode.MaxRune`, an `UntypedInt` static), and the wrapper's implicit conversions run in **both** directions with every numeric type. So in natural-inference position an all-const arm set infers the wrapper delegate — `var maxRune = (rune r) => Δunicode.MaxRune;` is `Func<rune, UntypedInt>`, rejected at the invariant-delegate `Map(maxRune, …)` call (CS1503) — and a mixed const/typed arm set (TestMap's `encode`, mixing `unicode.MaxRune`/`utf8.RuneSelf` with the `rune` parameter) has no unique best common type at all (CS8917). When any top-level return arm is a bare named untyped-const reference, the lambda states the declared return type explicitly and each arm converts in place:
```csharp
var maxFn = rune (rune _) => maxRune;
```
Same gates as the string arm: assignment position only (argument/return/composite-element literals are target-typed — no inference to fail), and a BASIC numeric result (a named numeric type would need a second user conversion the wrapper cannot chain — the `lambdaConstReturnCastType` named-type rationale). Literal-only arm sets stay inferred (no churn): an int literal is already C# `int`, a rune literal emits `(rune)'a'`, so `minRune := func(rune) rune { return 'a' }` infers correctly without a prefix.

A constant operator **expression** arm containing a named untyped constant counts the same as the bare reference (2026-07-17; the B7b gap — bytes TestMap's `invalidRune := func(r rune) rune { return utf8.MaxRune + 1 }` was the one remaining bytes build error): the operator result keeps the wrapper type, so the inferred delegate was `Func<int, UntypedInt>` against Map's `Func<int, int>` parameter (CS1503). The arm test (`returnArmKeepsUntypedWrapper`) walks paren/unary/binary trees for an untyped-named-const leaf, **except** when a constant fold (`overflowingConstLiteral` / `floatContextConstLiteral`) rewrites the whole arm to a plain literal — that emission is concretely typed and needs no prefix. All other gates unchanged. (Guarded by the `FuncLitUntypedConstReturn` behavioral test — the single-arm CS1503 shape, the mixed-arm CS8917 shape, an `int64` result with a beyond-int32 const arm, the const-expression arm (`return maxRune + 1`), plus literal-only and argument-position controls that must keep the plain form; output-compared vs Go.)

### A literal in GENERIC-RESULT inference position states its return type
The arms above all describe **natural-inference** position — a literal assigned to a `var`, where no
delegate target exists. A literal passed as an ARGUMENT is normally target-typed by its parameter and
needs no prefix, and the earlier rules say exactly that. There is one argument shape where that is
false: the callee is **generic** and the parameter's declared signature returns a **type parameter**
— `sync.OnceValue[T any](f func() T)`, `sync.OnceValues[T1, T2 any](f func() (T1, T2))`. There the
parameter type is not yet a concrete delegate; C# must infer the type argument **from the lambda's
own return expressions**, so the Go result type go/types already resolved is ignored. Two shapes then
break (both live in sync's `oncefunc_test.go`):

- **No arm yields a C# type at all** — a body terminated by `panic` (`func() any { calls++; panic("x") }`
  emits a statement lambda whose only exit is a `throw`), or one whose sole arm is an untyped `nil`
  (`return default!`). Inference has nothing to work from: CS0411 ×4.
- **An arm's NATURAL C# type differs from the declared Go result** — `func() int { return 42 }` is
  naturally `Func<int>`, where Go's `int` is `nint`: the wrong delegate is inferred and the
  declaration it initializes rejects it (CS0029).

A func literal in that position now states its declared result type, which fixes the type argument to
exactly Go's for every arm shape (so, unlike the natural-inference arms, no arm inspection is needed):

```go
var onceValue = sync.OnceValue(func() int { return 42 })
f := sync.OnceValue(func() any { calls++; panic("x") })
g := sync.OnceValues(func() (any, any) { buf[0] = 1; return nil, nil })
```
```csharp
internal static Func<nint> onceValue = Δsync.OnceValue(nint () => 42);
var f = Δsync.OnceValue(any () => { Ꮡcalls.Value++; throw panic("x"); });
var g = Δsync.OnceValues((any, any) () => { bufʗ3[0] = 1; return (default!, default!); });
```

The gate is the **result** position specifically. A type parameter appearing only in the func-typed
parameter's own PARAMETER list — the `slices.SortFunc(x, func(a, b E) int)` shape — is inferred from
the lambda's already-typed parameters and stays unprefixed; marking those would churn every such call
site in the corpus for no defect. Full-stdlib footprint: two files, both package-level
`sync.OnceValue` initializers (`internal/sysinfo`'s `CPUName`, `internal/syscall/windows`'s
`SupportUnixSocket`/`SupportTCPInitialRTONoSYNRetransmissions`). (Guarded by
`GenericResultLambdaInfer` — the `nint`/`any`/panic-terminated/two-result shapes, a concrete
multi-result instantiation, and the parameter-position negative control, output-compared vs Go.)

### A returned FUNC LITERAL is typeless in C#

Every arm above asks the same question — *does this return expression carry a natural C# type?* — and
each was written against the Go-side shapes seen so far: an untyped `nil`, a bare constant, an untyped
const wrapper. A Go **function literal** is a fourth shape, and it is typeless for a reason none of
those tests notice: it is fully typed in Go, and it renders as a bare C# lambda, which has no natural
type at all.

A function's own returns need no help — a declared C# result type target-types them, so a method whose
body sits in a frame simply returns the literal (`context`'s `afterFuncContext.AfterFunc`, which
returns a `Func<bool>` literal from a defer-holding body). The one site that does need help is **a
literal returned from inside another literal.** `lambdaConstReturnCastType` already casts a bare
integer literal returned inside a lambda for exactly this reason (CS8917). Its sibling
`lambdaFuncLitReturnCastType` names the declared result type of a returned func literal, under the same
gates — inside a lambda conversion only (a named function's returned literal is target-typed by its
declared C# return type), and only when the declared result is a NAMED func type, whose emitted
delegate name is what a cast needs:

```go
mergeCancel := func(ctx, cancelCtx Context) (Context, CancelFunc) {
    …
    return ctx, func() { stop(); cancel(Canceled) }
}
```
```csharp
(context.Context, Action) mergeCancel(context.Context ctx, context.Context cancelCtx) {
    …
    return (ctx, (Action)(() => {
        stopʗ1();
        cancelʗ3(context.Canceled);
    }));
}
```

(`mergeCancel` is itself only ever called, so it takes the local-function emission above — still a
lambda conversion, which is what the gate tests.) Without the cast the tuple has no natural type, so
neither does the enclosing conversion — CS8917 on the declaration and CS8130 at every deconstruction
of its result. Naming the type is also the more faithful rendering: Go's declared result there *is*
`CancelFunc`, a methodless func type, which renders inline as its base delegate `Action`. An UNNAMED `func() bool` result would need the synthesized
`Func<…>`/`Action` spelling and has no corpus site today, so it is deliberately left.

### Publicization decides WHAT a type's modifier is; the test-bridge arm only decides WHERE

`visitTypeSpec` writes a `[GoType]` declaration's access modifier from one of two sources, and they
answer different questions. `packagePublicizedTypes` answers *what* the modifier must be — an
unexported type reached by an exported field, var, or callable signature has to be `public` or C#
rejects the referrer (CS0050/CS0051/CS0052). `testInlineTypeAccess` answers *where* it is written: a
white-box bridge type carries its modifier inline rather than through `package_info.cs`'s
`<TypeAccessibility>` section, because its metadata anchor can be a different test class.

Asking the inline arm FIRST made it answer both — from the name alone — so a publicized bridge type
stayed `internal`. `context`'s internal test file declares `type testingT interface{…}` and the
exported `func XTestParentFinishesChild(t testingT)` that `x_test.go` calls; the publicization pre-pass
records `testingT` (it runs over the test-augmented package, so the exported `*types.Func` arm fires),
but the emission ignored it: `internal partial interface testingT` under a `public` method, CS0051 ×4.
Publicization now outranks, and the inline arm supplies the DEFAULT:

```csharp
[GoType] public partial interface testingT {   // was: internal
```

This is why `signatureReferencesUnexportedProductionType` (which downgrades an exported *test-file*
function whose signature names an unexported PRODUCTION type) is correctly restricted to production
types: a test-declared type is meant to be handled by publicization, and now is.

### A white-box PRODUCTION↔PRODUCTION pointer pair is already implemented — do not record it again

Under the `whitebox-reference` test model the internal bridge is the SAME Go package as production, so
a `*prodT → prodIface` cast inside an internal `_test.go` reads as local and records its own
`[assembly: GoImplement<T, Iface>(Pointer = true)]`. Production, though, is a **referenced assembly**
that already generated that adapter from its own record — and `InternalsVisibleTo <assembly>.tests`
makes even an unexported adapter class reachable. The duplicate record makes go2cs-gen emit a SECOND
adapter under a test anchor, and that copy resolves its forwarding members in the TEST class's scope:
`context`'s `contains(pc.children, cc)` converts `*cancelCtx`/`*timerCtx` to `canceler` for a map key,
and the duplicate bound `Done` to the unrelated `afterFuncContext.Done` extension (CS1929) while
emitting `cancel` with an **empty body** — a silently degraded override, not merely a build error.

The fix suppresses only the RECORD, which is what makes it small: `resolveAdapterNameMarkers` resolves
a pair that reached no record to the unqualified name it would have had, and that name is production's
own adapter, so the cast site repoints with no other change.

```csharp
!contains((~pc).children, new global::go.context_package.cancelCtxжcanceler(cc))   // production's, not a copy
```

It is gated on production ACTUALLY carrying the pair — its `package_info.cs` is loaded by
`convertTestVariant` into `importedPointerImplements` — never assumed: a pair only the test converts
still needs its local record. Reaching that set also required `canonicalRecordIfaceName` to strip a
leading `global::`, which names no package and never appears in a parsed record; the deliberate
non-collapse it documents is untouched, since a genuinely foreign pair still keys as
`net.http_package.ΔHandler` against the record's `http_package.ΔHandler`. This is the POINTER twin of
the value arm's `whiteboxProductionTarget` carve-out — note the pointer target arrives as a
`*types.Pointer`, so the shared `whiteboxProductionTarget` flag (computed from the unwrapped VALUE
form) is structurally false there and the check must unwrap and ask directly.

### A GoImplement record is gated on the method set actually satisfying the interface
Every `[assembly: GoImplement<T, Iface>]` record makes the `ImplementGenerator` emit implementation glue whose members forward to T's like-named methods — so a record whose Go method set does NOT satisfy the interface generates a forwarder to a method that does not exist. The corpus case: net/http's `err = http2GoAwayError{LastStreamID: …, ErrCode: cc.goAway.ErrCode, …}` — the keyed composite's sparse-array `ident` context leaks the `error`-typed LHS onto each FIELD value, and the `ErrCode` field's value recorded `GoImplement<http2ErrCode, error>` even though `http2ErrCode` has only `String()`/`stringToken()` (its generated `Error() => this.Error()` was CS1929). `convertToInterfaceType` now folds a `types.Implements` check over the recorded form's method set (T for a value record, `*T` for a `ж<T>` record) into `recordableBase`, which gates both the record and the matching adapter-wrapping emissions. A conversion the Go checker admitted always passes the check, so the gate can only drop pairs a caller composed from mismatched types; a type-param-carrying target skips the check (`types.Implements` is undefined for uninstantiated generics, and the open-generic conversion emission must stay). The full-stdlib A/B for this change is exactly one removed line — the false `http2ErrCode` record. (Guarded by the NEGATIVE `KeyedLiteralIfaceAssign` behavioral test: a keyed literal assigned to an `error` variable whose field-value type has `String()` but no `Error()` — a reintroduced record fails the compile phase.)

### Promoted methods through a FOREIGN pointer embed forward via the embedded box
A struct embedding a FOREIGN pointer (`net/http`'s `http2timeTimer struct { *time.Timer }`, `net/http/internal`'s `FlushAfterChunkWriter struct { *bufio.Writer }`, `bufio.ReadWriter { *Reader; *Writer }`) promotes the embed's pointer-receiver methods, but those land as ж-extensions visible to the consuming assembly only through METADATA — direct-ж primaries (`Reset(this ж<Timer> …)`) and the public `RecvGenerator` twins of `[GoRecv]` methods (`Write(this ж<Writer> …)`). The `ImplementGenerator`'s syntax-tree hop scan saw none of them, so the value-form partial deref'd to the value (`this.Timer.Value.Reset(d)` — CS1929, the extension receiver strands) and the FOREIGN-struct pointer adapter fell back to the struct itself (`m_box.Value.Write(p)` — CS1061, `ReadWriter` declares nothing). Both arms now probe the embed's type SYMBOL: the single-hop paths union the foreign element's metadata box methods into the hop's box-method set (`this.Timer.Reset(d)`; `m_box.Value.Writer` in the local pointer arm), and the foreign-struct pointer arm routes each member still on the plain fallback through the UNIQUE pointer embed whose metadata box methods declare it (`m_box.Value.Writer.Write(p)`; Go's depth-one promotion ambiguity rules make the unique-embed requirement faithful). An embed package class outside extension-lookup reach (not the emitting namespace, the shared root `go`, or an enclosing segment) forwards through its package-class static with the box as the receiver argument, mirroring the foreign-extension arm. (Guarded by the `ForeignPtrEmbedIfaceLib`/`ForeignPtrEmbedIfaceUser` pair — a local struct embedding a foreign pointer adapted by value AND by pointer, plus a foreign two-pointer-embed struct adapted by pointer, output-compared vs Go.)

### Promoted methods through embedded INTERFACE fields route per-member to the declaring field
A struct whose embeds are INTERFACE fields (httputil's `dumpConn struct { io.Writer; io.Reader }` adapted to `net.Conn`) satisfies interface members through the fields' method sets. The pointer adapter's embedded-interface-field arm was gated to a SINGLE field, so a multi-field struct got no forwarding at all (`m_box.Read(…)` — CS1061). The arm now resolves per member: each still-unbound interface member forwards through the UNIQUE embedded field whose interface declares it (`m_box.Value.Reader.Read(p)` / `m_box.Value.Writer.Write(p)`); a member declared by several fields is left unbound (Go's promotion ambiguity rules reject it unless the struct overrides, and a struct override is already resolved earlier). The single-field behavior is unchanged (zip's `nopCloser`, slogtest's Δ-renamed `Handler` field). (Guarded by the `IfaceFieldEmbedAdapter` behavioral test — a two-interface-field struct adapted by pointer to a third interface needing members from both fields plus one declared on the struct, output-compared vs Go.)

### A pointer parameter used only through its box gets no deref VALUE alias
Every named pointer parameter is emitted as its box `ж<T> Ꮡp` with an entry-time value alias
`ref var p = ref Ꮡp.Value` (so a value use `p.field` reads through `p`). But a parameter that the body
touches **only** through its box — `unsafe.Pointer(p)` → `new @unsafe.Pointer(Ꮡp)`, `p == nil` →
`Ꮡp == nil`, or passing `p` on as a `*T` argument → `Ꮡp` — never references that value alias, so the
alias is a dead local that nonetheless **dereferences the box** at function entry. When the argument is
nil this NREs even though Go never touches the pointee: `syscall.writeFile(…, overlapped *Overlapped)`,
called with a nil `overlapped` and using it only as `unsafe.Pointer(overlapped)`, crashed at
`ref var overlapped = ref Ꮡoverlapped.Value` — the failure of any converted `fmt.Println` whose stdout is
a pipe (`syscall.Write` → `writeFile`). The converter already skips the alias for an **unnamed/blank**
pointer param (never referenced); this extends it to a **named** param whose value alias is likewise
unreferenced. After the body is converted, `bodyReferencesIdentAsValue` scans the emitted body text for
the value-alias name as a **standalone identifier** — the address marker `Ꮡ` is a Unicode *letter*, so
the box form `Ꮡp` is excluded by the preceding-letter boundary, while a genuine value use always emits
the bare name and matches. The scan only ever ADDS spurious matches (a field selector `x.p`, a string, a
comment), which keep the alias, so a live alias is never dropped (no CS0103) — it removes strictly unused
locals. Because a param with no alias leaves `implicitPointers` empty (which otherwise triggers the
signature rebuild that renames pointer params to the box `Ꮡ<name>` the body references), a
`skippedDeadPointerAlias` flag forces that rebuild. Full-stdlib/behavioral blast radius is broad (72
behavioral files — many functions forward a pointer param onward as a pointer), all pure alias removals.
This shares its root with the receiver case below (an eager deref alias NREs on a nil pointer); the
converse **live**-alias nil case — invoking a pointer method/function that *does* dereference a nil
receiver/param — still NREs at the alias, awaiting the nil-safe `DerefOrNil` extension. (Guarded by the
`DeadPointerParamAlias` behavioral test — a pointer param used only in `p == nil`, one forwarded as a
pointer, and one dereferenced (alias kept), each called with nil and non-nil, output-compared vs Go; the
nil calls NRE'd before the fix.)

⚠ **Keeping a dead alias is not free, so the scan excludes the one spurious match that is systematic: a
C# NAMED-ARGUMENT LABEL** (`isNamedArgumentLabel`). The converter renders a Go composite literal's field
keys as named arguments to the fieldwise constructor go2cs-gen generates, and Go's own idiom is to name
the field after the value it is initialized from — so a pointer parameter whose name matches one of the
literal's FIELDS matched the whole-word scan on the label alone. internal/concurrent's
`newIndirectNode(parent *indirect) { return &indirect{node: …, parent: parent} }` never dereferences
`parent` (the C# passes the box, `parent: Ꮡparent`), yet `parent:` kept the alias alive;
`ref var parent = ref Ꮡparent.Value` then deref'd the box at ENTRY, and the ROOT node — created as
`newIndirectNode(nil)`, a legitimately nil parent — threw inside `NewHashTrieMap`, taking down `unique`'s
package initializer, `net/netip`'s, and every dependent (found through encoding/gob's `TestNetIP`). A
label is the identifier immediately followed by ONE `:` — never `::` — whose preceding non-space
character opens or continues an argument list; every other colon form the converter emits is excluded by
that (a `case X:` arm and a goto label are preceded by a keyword or a statement boundary, an interpolated
format specifier `{x:F2}` by a brace, and a conditional's `cond ? a : b` spaces its colon). A parameter
genuinely used elsewhere still matches there, so this discards the label occurrence and never the scan.
Whole-stdlib A/B (two seeded reconverts on the two binaries, 302 packages): **39 files**, every diff hunk
the removal of one dead `ref var` line and nothing else, and the reconverted corpus builds 304/304 with
**0 errors** — the removed aliases are all genuinely unreferenced. (Guarded by the same
`NilPointerParamUnsafePointer` behavioral test, extended with the composite-literal shape and a
dereferencing positive control whose alias must survive; the exclusion's own boundaries — `::`, a
`case X:` arm, a spaced conditional colon, an interpolated format specifier, and a label that does not
mask a real use elsewhere — are pinned by the `TestBodyReferencesIdentAsValueIgnoresNamedArgumentLabels`
converter unit test, since a behavioral program cannot put most of them in that position.)

### A pointer RECEIVER compared to `nil` compares its box, not its deref'd value
A method with a pointer receiver — `func (f *File) checkValid() error { if f == nil { … } }` — is emitted
as `checkValid(this ж<File> Ꮡf, …)` with the body alias `ref var f = ref Ꮡf.Value`. Go's `f == nil` is a
**pointer** comparison (nil-pointer check — and Go legitimately calls methods on nil-pointer receivers), so
it must compare the box `Ꮡf == nil`, not the deref'd struct value `f`. Emitted in value form, `f == nil`
binds the generated `File.operator==(File, NilType)` — which compares against `default(File)` and, for a
**promoted-embed** struct (`File` embeds `*file`), dereferences that zero value's null embed box → a
`NullReferenceException` (the first crash of a converted `fmt.Println`, via `os.Stdout.Write` →
`checkValid`); even for a plain struct it is the wrong answer (`&box{}`, a non-nil pointer to a zero struct,
compares *equal* to `default(box)` → `true` where Go gives `false`). The converter already forced the box
form for a deref'd pointer **parameter** (`isDerefdPointerParamIdent`, driving the `==`/`!=` operand
context in `convBinaryExpr`); the receiver is deliberately not a "parameter" in that model
(`paramNames` excludes the receiver), so it needs its own recognizer, `isDerefdPointerReceiverIdent`
(object-identity match via `isPointerReceiver`/`identResolvesToReceiver`, so a local shadowing the receiver
name keeps its own render). It is scoped to the comparison operands only — unlike a pointer parameter the
receiver is **not** folded into `nilSafePtrParamNames`, so its deref-alias form is unchanged (only the
receiver's `==`/`!=` operand switches to the box); `convIdent` renders `Ꮡf` for the receiver through its
existing direct-ж-receiver arm. Like a deref'd pointer parameter, the box form applies to **every**
`==`/`!=` comparison, not just against nil: a pointer receiver can only be `==`-compared to another
pointer or to nil, so the box is always the correct (pointer-identity) operand — `func (b *Reader)
Reset(r io.Reader) { if b == r … }` (bufio) becomes `AreEqual(Ꮡb, r)`, comparing the pointer to the
interface's held pointer, where the pre-fix `AreEqual(b, r)` compared a *struct value* to the interface
(never equal — a latent recursion-guard bug the fix also closes). The full-stdlib A/B is small and
mechanical — 161 receiver operands across 77 files gain a `Ꮡ` box (155 nil-checks, 6 pointer-identity),
every changed line adding only the box.
(Guarded by the `PointerReceiverNilCompare`
behavioral test — a plain-struct `*box` where `&box{}` must compare `!= nil`, `== nil`/`!= nil` receiver
methods, and a promoted-embed `*embedder` whose pre-fix value comparison NRE'd, output-compared vs Go; and
by the re-baselined `RingPointerMethods` whose `r != nil` receiver walk now renders `Ꮡr != nil`.)

### A pointer RECEIVER's deref alias is nil-DEFERRING — the panic moves to the body, it does not vanish
Go legitimately **calls** methods through a nil pointer: the method RUNS, and the nil-pointer panic
happens only where the body actually dereferences the pointee. The emitted entry alias
`ref var b = ref Ꮡb.Value;` instead dereferenced at ENTRY, so every nil-tolerant method panicked
before it began — whichever way its guard is spelled:

| Guard shape | Go | pre-fix C# |
|:--|:--|:--|
| INLINE — `func (b *Buffer) String() string { if b == nil { return "<nil>" } … }` (bytes `TestNil`) | returns `"<nil>"` | entry panic |
| DELEGATED — `func (f *File) Chdir() error { if err := f.checkValid("chdir"); … }`, where `checkValid` is what asks `f == nil` (os `TestNilFileMethods`, all fifteen `*File` methods) | returns `ErrInvalid` | entry panic |
| NONE, but a side effect first — `fmt.Println(…)` then `f.name` | prints, THEN panics | entry panic, nothing printed |

The first row was closed in 2026-07 by folding a **nil-COMPARED** receiver into `nilSafePtrParamNames`
so its alias took the nil-SAFE `DerefOrNil()`. That could not close the other two, and widening it to
every receiver was the wrong instrument: `DerefOrNil()` hands back a shared throwaway `default(T)`
slot, so an unguarded deref reads a **silent zero where Go panics** — acceptable only where the
converted guard provably excludes the read (the nil-terminated pointer walk it exists for), never as a
corpus-wide default. That widening was measured and reverted.

The instrument that works is a **nil-DEFERRING** accessor, `DerefOrNull()`, which binds
`Unsafe.NullRef<T>()` for a nil box. A null ref is legal to HOLD and to pass on as `ref T`; it faults
on USE. So the panic is not discarded and not moved earlier — it lands exactly where Go's does:

```csharp
public static (nint n, error err) Read(this ж<File> Ꮡf, slice<byte> b) {
    ref var f = ref Ꮡf.DerefOrNull();          // binds; never throws
    {
        var errΔ1 = Ꮡf.checkValid(readˢ);      // through the BOX — Go's guard, reached
        if (errΔ1 != default!) { return (0, errΔ1); }
    }
    (n, var e) = Ꮡf.read(b);
    return (n, f.wrapErr(readˢ, e));           // the first real deref — panics here, as Go does
}
```

The first field read, field write, or whole-struct copy through that ref raises
`NullReferenceException`, which `RuntimeErrorPanic.TryAsPanic` already maps to Go's own
`runtime error: invalid memory address or nil pointer dereference` — recoverable and printed
verbatim. Measured, not assumed: a synthetic struct whose field sits 200 KB past the null page still
faults as a clean `NullReferenceException` (the JIT emits an explicit check rather than relying on the
64 KB guard page), and a converted Go struct cannot reach even that offset — Go's inline `[N]T` becomes
a golib `array<T>`, an 8-byte managed reference — so there is no null-page cliff to fall off.

**Where it is emitted.** The entry alias exists in **two** places and both take the accessor, or the
fix is half-done:
- the converter's own preamble for a direct-ж receiver (`visitFuncDecl`, unconditionally for
  `i == 0 && Recv != nil && directBoxReceiver`; plus a repointed receiver's re-alias in
  `visitAssignStmt`, so the two halves of one alias cannot disagree);
- **go2cs-gen's `ReceiverMethodTemplate`**, the bridge that reaches a `ref T`-receiver method through a
  box. Left eager, that bridge panicked one call frame *earlier* than Go — the `ValAnnounce` arm of the
  guard below caught exactly this and was the reason the generator half was found at all.

Because the accessor is unconditional there is no predicate to get wrong, and
`isComparedDirectBoxReceiverIdent` — the 2026-07 receiver-specific arm of `collectNilSafePtrParams` —
is subsumed and deleted. A non-nil receiver is unaffected: `DerefOrNull()` routes it to `ValueSlot`,
the identical real slot, which additionally subsumes the `isInherentlyHeapAllocatedType` →
`.ValueSlot` receiver arm above (same slot, and now the genuinely-nil case is handled too rather than
silently read). The scan that remained after this fix covered pointer PARAMETERS only — and the
section below is why it does not exist at all any more.

(Guarded by the `NilReceiverMethods` behavioral test, output-compared vs `go run`: a delegated
`checkValid`-style guard that must return the error, an unconditional deref that must panic with Go's
message, a side effect that must be observed BEFORE that panic, and a non-nil receiver read through
both emission shapes — each panic case run through both the direct-ж preamble and the generated
`ref`-receiver bridge. `PointerReceiverNilCompare` and bytes' `TestNil` continue to cover the inline
guard.)

### A pointer PARAMETER is nil-deferring for exactly the reason a receiver is
Go's nil rule does not distinguish the two. Passing a nil `*T` to a function is as legal as calling a
method through one: the body RUNS, and the nil-pointer panic happens only where the body actually
dereferences the pointee. So the entry alias for a pointer PARAMETER takes the same
`DerefOrNull()` the receiver does, unconditionally, with no admitting analysis — because the accessor
is faithful whether or not the body guards.

Getting here took three arms of a body ANALYSIS, each one a real fix for a real crash and each one
provably incomplete, which is the argument for retiring all of them at once:

| Arm | The shape it admitted | Why it could not be the answer |
|:--|:--|:--|
| nil-COMPARED param (`collectNilSafePtrParams`) | `for p != nil { …; p = p.next }`; `if p == nil { … }` | a body that never spells the comparison is not a body that cannot take nil |
| nil-ARGUMENT call site (`collectNilArgPtrParams`) | `digits(ch, 10, nil)` — text/scanner's optional out-param | SAME-package call sites only; the converter sees one package at a time, so a cross-package nil argument stayed broken |
| RE-POINTED before first read (`reassignedBeforeDerefParamName`) | `l = l.get()` — Go's nil-receiver NORMALIZATION idiom (`time.Location.lookup`, whose entry fault cost seven of `time`'s verdicts) | narrow by construction: first top-level statement only, and every use inside it non-dereferencing |

The shape none of them could reach is the one that broke `net`. `internal/concurrent`'s
`newIndirectNode(parent *indirect[K, V])` STORES its parameter and never dereferences it, and
`net`'s package initializer reaches it through `netip` → `unique` as `newIndirectNode(nil)` — a
perfectly ordinary nil argument, from another package, with no comparison anywhere in the callee. The
eager `ref var parent = ref Ꮡparent.Value;` faulted at entry, inside a static constructor, so every
`net` test failed before the first one ran (`net` compiled with 0 errors and scored **0/138**).

Nothing analyzable distinguishes that callee from one that derefs immediately. What distinguishes them
is what the BODY does, at the point it does it — which is precisely what the nil-deferring accessor
defers to:

```go
// internal/concurrent/hashtriemap.go
root: newIndirectNode[K, V](nil),        // NewHashTrieMap's initializer, reached from net's .cctor
...
func newIndirectNode[K, V comparable](parent *indirect[K, V]) *indirect[K, V] {
	return &indirect[K, V]{node: node[K, V]{isEntry: false}, parent: parent}
}
```
```csharp
internal static ж<Δindirect<K, V>> newIndirectNode<K, V>(ж<Δindirect<K, V>> Ꮡparent)
    where K : /* comparable */ new()
    where V : /* comparable */ new()
{
    ref var parent = ref Ꮡparent.DerefOrNull();   // was: ref Ꮡparent.Value — NRE in net's .cctor

    return Ꮡ(new Δindirect<K, V>(node: new node<K, V>(isEntry: false), parent: Ꮡparent));
}
```

**What the accessor costs.** For a NON-nil pointer, nothing observable: `DerefOrNull()` routes to
`ValueSlot`, the identical real slot, so every read, every write-through and every re-alias behaves
exactly as `.Value` did. For a nil pointer it binds `Unsafe.NullRef<T>`, which is legal to hold and
faults on USE — so an unguarded deref still raises Go's
`runtime error: invalid memory address or nil pointer dereference` (via
`RuntimeErrorPanic.TryAsPanic`), recoverable, at the body's own deref point, AFTER any side effect the
body performed first. The retired nil-SAFE accessor could not say that: its shared `default(T)` slot
read a silent zero where Go panics, which is why it was only ever admissible under a proof.

**One shape, everywhere a pointer is aliased.** The unification also takes the pointer-reassignment
re-alias in `visitAssignStmt` (single-assign, tuple-deconstruction, and the `p = nil` repoint alike):
a repoint is not a dereference in Go, so re-aliasing must not fault, and the two halves of one alias
must not disagree about whether nil is legal to hold. And it takes the `isInherentlyHeapAllocatedType`
→ `.ValueSlot` arm at THIS site, which is the one place the unification goes beyond swapping an
accessor. `.ValueSlot` remains type-selected everywhere it belongs — a box-of-pointer LOCAL, a
named-result box, `heap(out …)`, the reflection bridge's field paths — but at an entry alias it was
selected AFTER the nil-safe analysis, i.e. the old code already ranked nil-ability above pointee-kind.
Keeping it ranked first under an unconditional nil policy would have inverted that order and handed 9
corpus aliases across 8 files (`internal/weak`'s `ptr`, runtime `mbitmap`'s `header`, dwarf's
`fixups`, …) a NEW entry-time fault on exactly the nil arguments the previous converter tolerated —
measured, and the reason the arm is not selected here.

**Measured footprint** (seeded whole-stdlib A/B reconvert, base vs fixed, 2026-08-02): **457 files,
2,551 lines, 2,555 accessor sites — 100% a single shape**, the accessor token at a
`= ref <box>.<accessor>;` position, with zero unclassified lines and no file changing its line count:

| Transition | Sites |
|:--|--:|
| `.Value` → `.DerefOrNull()` | 2,079 |
| `.DerefOrNil()` → `.DerefOrNull()` | 413 |
| `.ValueSlot` → `.DerefOrNull()` | 59 |

Corpus-wide the entry/re-alias census moves from `Value` 2,849 / `ValueSlot` 81 / `DerefOrNil` 420 /
`DerefOrNull` 2,030 to `Value` 766 / `ValueSlot` 22 / `DerefOrNil` 0 / `DerefOrNull` 4,585 — the
remaining `.Value` are genuine USE sites, where Go does panic. (Seven `DerefOrNil()` sites survive in
committed `*_test.cs` under `container/ring`, `go/token`, `index/suffixarray` and `testing/quick`: a
`-stdlib` reconvert does not re-emit banked test sources, so they level at each package's next
`-tests` run.) The behavioral corpus re-baselines in the same one shape — 71 `.cs`, 138 lines, 69
goldens, every added line an accessor swap. The converter loses 382 net lines: the accessor constant,
three analyses, their helpers, the package-wide pre-pass and the visitor state behind them.

(Guarded by the `NilPointerParamMethods` behavioral test, output-compared vs `go run`: a nil argument
through a DELEGATED guard that must return the error, a nil argument STORED and never dereferenced —
the `newIndirectNode` shape — an unconditional deref that must panic with Go's message, a side effect
that must be observed BEFORE that panic, a nil-terminated walk, a `*error` parameter that legally
HOLDS nil, the normalization idiom, and every non-nil argument unchanged. Neuter-proven: restoring the
eager param arm diverges from Go on 42 output lines. `GuardedNilPointerParamDeref`,
`PointerParamNilWalk`, `NilReceiverNormalization` and `PointerToInterfaceParamDeref` continue to cover
the shapes the retired arms were built for.)

### A receiver or parameter RE-POINTED before first use — the normalization idiom
Go's other nil idiom does not test the pointer at all: it *normalizes* it, by re-pointing through a
helper that does the testing. `time`'s `Location.lookup` is the canonical case — `Time{}` carries a nil
`*Location` meaning UTC, and `get` maps nil to `&utcLoc`:

```go
func (l *Location) lookup(sec int64) (name string, offset int, start, end int64, isDST bool) {
	l = l.get()
	if len(l.zone) == 0 { … }
```

No comparison predicate can see this — `lookup` never writes `l == nil` — so under the eager alias the
receiver **faulted before its first statement**, where Go returns UTC. That one site cost seven of
`time`'s test verdicts (`TestDefaultLoc`, `TestSecondsToUTC`, `TestNanosecondsToUTC`, `TestParse`,
`TestTimeGob`, `TestTimeIsDST`, `TestZoneBounds`), every one of them dying at entry.

It was closed first by a dedicated predicate (`reassignedBeforeDerefParamName`: the FIRST top-level
body statement that mentions the pointer must be an `=` that re-points it, with every use of it inside
that statement leaving the pointee untouched — a pointer-receiver method call like `l.get()`, a nil
comparison, or the bare pointer as an argument; a field selection, a value-receiver method, or a `*p`
disqualified it). That predicate is gone: the unconditional nil-deferring alias covers the idiom
without needing to recognize it, and covers the variants it could not admit (a normalization on the
SECOND statement, or one nested in an `if`). Both halves now read the same way:

```csharp
internal static (@string name, nint offset, int64 start, int64 end, bool isDST) lookup(this ж<ΔLocation> Ꮡl, int64 sec) {
    …
    ref var l = ref Ꮡl.DerefOrNull();          // binds for Time{}'s nil loc; no fault at entry
    Ꮡl = Ꮡl.get(); l = ref Ꮡl.DerefOrNull();   // the repoint is not a deref either
```

One subtlety the predicate era got right and this must keep: a normalizer that CAN return nil
(`GenericFuncCall`'s `renew` — `p = escape(p); *p += 10`, where `escape` is identity) must still panic
at the `*p`, and it does — the re-alias binds a null ref and the dereference through it faults with
Go's message, exactly where Go's does. (Guarded by the `NilReceiverNormalization` behavioral test —
normalize-then-read, normalize-then-*write* through the re-aliased receiver with a read-back proving
the real slot was addressed, the same shape on a nil-legal pointer *parameter*, and a control that
reads BEFORE normalizing and still panics through a nil receiver exactly as Go does.)
### A reinterpreted raw address ALIASES native memory instead of boxing a copy
`(ж<T>)(uintptr)` is the reinterpret seam: it turns a raw address back into a pointer. It used to
box a **copy** of the pointed-at value —

```csharp
public static unsafe explicit operator ж<T>(uintptr value) => new ж<T>(*(T*)value.Value);
```

— which silently discarded the address. That is fine only for an immediate single read. It makes
three things impossible: pointer arithmetic (there is no address left to advance), observing writes
that native code makes afterward, and — worst — handing the pointer **back** to the OS, which then
operates on the address of a *managed box field* instead of the native block.

`syscall.Environ` does all three. It walks the `GetEnvironmentStringsW` block and frees it:

```go
envp, e := GetEnvironmentStrings()
defer FreeEnvironmentStrings(envp)
for *envp != 0 { … end = unsafe.Add(end, size) … }
```

Converted, the walk scanned the GC heap and the deferred `FreeEnvironmentStringsW` asked Windows to
free GC memory — an outright `STATUS_HEAP_CORRUPTION` (0xC0000374) process kill. `os.Environ()`
alone reproduced it, so nothing that reads the environment could run.

`ж<T>` now has a fourth reference kind alongside the standard value, struct-field and array-element
refs: a box that **aliases a native address**. `Value`/`ValueSlot` read that memory through
`Unsafe.AsRef`, `IsNull` is address-based (address 0 is the nil pointer, matching Go's
`(*T)(unsafe.Pointer(uintptr(0))) == nil`), and the `uintptr`/`void*` operators round-trip the
address **exactly** — which is what Go's `uintptr(unsafe.Pointer(p))` guarantees. `unsafe.Add`,
`unsafe.Slice` and `unsafe.String` honor the kind.

`unsafe.Add` also gained an `unsafe.Pointer` overload. Go's `unsafe.Add` is **byte** arithmetic and
its argument is always an `unsafe.Pointer`, but golib models `unsafe.Pointer` as `ж<uintptr>` whose
*value* is the address — so the generic `ж<T>` overload, which resolves a managed array-element
reference, found none and returned a **nil** pointer. Stepping through a native block dereferenced
address 0 on the very first step.

**Known limit:** `unsafe.Slice`/`unsafe.String` over a native address *snapshot* the memory into a
managed buffer rather than aliasing it the way Go does, so writes through the result do not reach the
native memory. That is sufficient for reading a block a syscall returned (the `Environ` shape) and is
where the seam still differs from Go.

### An address of MANAGED storage that outlives its statement must carry a PIN

The native-address box above is exactly right when the address IS native memory — there is nothing
behind it the collector could move. It is a **dangling pointer** when the address points into managed
storage, and the reinterpret fallback produces precisely that: where `(*U)(unsafe.Pointer(p))` cannot
alias `p`'s storage in the managed model, golib names it by address instead
(`PointerExtensions.Reinterpret` → `(ж<U>)(uintptr)box`), and the `uintptr` operator's

```csharp
fixed (void* ptr = &value.Value)
    return (uintptr)ptr;
```

pins for **that statement** and no longer. The derived pointer outlives it. Once a collection moves
the storage, reads through the pointer return whatever now occupies the old address — and **writes
land in whatever now owns it**. The second is the one that matters: it is not a wrong value, it is
silent heap corruption, and the crash surfaces later, somewhere unrelated. The corpus's clearest
instance of the shape is `os_windows_test.go`'s `createMountPoint`, whose four `uint16` field stores
go through a `[]byte` scratch buffer addressed as a reparse record:

```go
byteblob := make([]byte, buflen)
buf = (*windows.MountPointReparseBuffer)(unsafe.Pointer(&byteblob[0]))
buf.SubstituteNameOffset = target.substituteName.offset   // … and three more
```

```csharp
var byteblob = new slice<byte>(buflen);
buf = Ꮡ(byteblob, 0).Reinterpret<byte, windows.MountPointReparseBuffer>();
buf.Value.SubstituteNameOffset = target.substituteName.offset;
```

**The rule: the pin's lifetime is the DERIVED POINTER's, not the address-taking statement's.** The
fallback now asks the source box for a pinned address (`ж<T>.TryPinnedReinterpret`), and the derived
box OWNS that pin — a `PinnedBuffer.PinOnly` handle held in the box's `m_pin` field and released by
its finalizer when the box is collected. This is the same field and the same idiom as the fixed-array
syscall-buffer pin (`pinnedArrayData`, above); the two uses are disjoint, since a native box never
takes the lazy one.

Only an **array/slice-element** reference can be pinned, and that is not a shortcut — it is the only
reference kind whose storage is an object the runtime can be asked to hold still. A native alias has
nothing managed behind it; a nil box has no storage; and the storage of a standard heap box and of a
struct-field reference alike is a field of a `ж<T>`, which holds delegates and a nullable tuple and so
is never blittable — `GCHandle` refuses to pin it. Those kinds keep the pre-existing address route, so
the change is strictly additive: where a pin cannot be taken, behavior is what it was, never something
newly wrong. That is also what keeps `reflect`'s prefix-downcast idiom
(`(*structType)(unsafe.Pointer(t))` over a `*abi.Type`, structurally unrepresentable and deliberately
on the address route) working unchanged — a blanket "fail loudly" was never available.

The pin is **cross-checked before it is trusted**. It is taken on the backing store
`CanonicalElement` names, which proves something only if the referent really lives inside that object,
so the address reached through the box's own value slot must be the same byte as the address of that
backing's element; a view whose `Source` is a detached copy fails the test and gets no pin.

Guarded by `tests/Behavioral/ReinterpretPinLifetime`, which is deterministic in both directions — it
writes through the derived pointer before and after enough allocation churn to move and recycle the
buffer, and reads back through both the derived pointer and the original slice. Pre-fix C# printed
`read: false true true` / `write: false false false false false false` on 5 of 5 runs where Go prints
all `true`; post-fix it matches Go on 8 of 8. Its sibling `ReinterpretPointerLifetime` guards the
other half of the same contract — the ALIASING route, for reinterprets the managed model *can*
represent.

**What this is NOT evidence of.** `os`'s test host was separately observed dying with an
`ExecutionEngineException` whose crash site moved between runs, and `createMountPoint` was the
standing suspect. A pre-fix control run of the whole `os` suite at `af5df9e16` (golib stashed back to
base, the rebuilt `golib.dll` verified to lack the fix) reproduced no such crash, and two complete
post-fix runs bracket its agreeing count from both sides, so that attribution is retracted — see the
`os` section of [`docs/phase4/BOARD-next-validation-candidates.md`](phase4/BOARD-next-validation-candidates.md).
The pin defect is real and deterministic on its own evidence; it simply was not shown to be that
host-killer.

**Not fixed by this, and a different class:** a destination struct holding a managed reference where
Go has an inline array (`PathBuffer [1]uint16` → `array<uint16>`) still fabricates an object reference
out of whatever bytes sit at that offset when the field is read. Pinning makes those bytes the *real*
buffer's rather than recycled memory, but a fabricated reference is a CLR type-safety break either
way. That is the raw-metal-on-non-native-types fork (`os.readReparseLink`'s remedy is a hand-owned
decode; `os_windows_test.go`'s `createMountPoint` is test code that cannot be hand-owned).

### `unsafe.Slice` over MANAGED element storage ALIASES it

The snapshot above is the right answer for a native address and the wrong one for the far commoner
shape: `unsafe.Slice(&s[i], n)`, where the pointer addresses an element of a managed slice or array.
Go's result shares that storage, so writes through the rebuilt slice must land in the original
backing — and the snapshot silently swallowed every one of them. crypto/subtle is the case that
exposed it: `XORBytes` hands `xorBytes` bare pointers, which rebuilds its three slices and writes the
whole result through `dst`, so **`XORBytes` wrote nothing at all** (its test matrix compared `dst`
against its untouched `0xdd` fill).

`ж<T>` answers with the window when it has real managed element storage
(`TryGetElementWindow`): the referent is reduced through the same `CanonicalElement` mapping pointer
equality uses — so a pointer taken through a re-sliced view addresses the same absolute element Go's
would — and the result is a `slice<T>` over that backing with `len == cap == n`, exactly Go's shape.
A heap box, a struct-field ref, or a REINTERPRETING pointer (a `(*U)(unsafe.Pointer(&b[0]))` over a
differently-typed array) has no such storage and keeps the snapshot; a `T[]` view over another
element type does not exist in the managed model.

That last exclusion is why `crypto/subtle/xor_generic.cs` is **hand-owned**
(`[module: GoManualConversion]`). Its word-at-a-time loop reinterprets the byte slices as
`[]uintptr` —

```go
func words(x []byte) []uintptr {
	return unsafe.Slice((*uintptr)(unsafe.Pointer(&x[0])), uintptr(len(x))/wordSize)
}
```

— which the converted form can only snapshot, so for every length that is a multiple of 8 the XOR
went to a detached buffer and `dst` stayed untouched, while other lengths landed only their trailing
`n % 8` bytes. The hand-owned file does the same reinterpret the managed way,
`MemoryMarshal.Cast<byte, ulong>` over the slices' own spans — a genuine aliasing view, so the word
writes land in place. It keeps Go's word-at-a-time behavior (and with it the performance contract
crypto/cipher's CTR and GCM modes depend on) and drops only Go's `supportsUnaligned`/`aligned` gate,
which exists for architectures whose unaligned word loads fault. (Validated by crypto/subtle's own
suite: 7/7, no disclosures, over the full 1..1024 × 8 × 8 × 8 alignment matrix.)


### A reinterpret of a MANAGED pointer aliases the box — it never round-trips through the address
The section above is about a pointer whose source genuinely *is* an address. The mirror case is
`(*U)(unsafe.Pointer(p))` where `p` is an ordinary Go pointer `*T` — the shape `reflect` uses to view
one struct as another (`toRType`: `(*rtype)(unsafe.Pointer(t))`). Both pointee types are managed, so
there is no native memory anywhere in the expression, yet the emission routed through the raw-address
seam anyway:

```csharp
return (ж<view>)(uintptr)(new @unsafe.Pointer(Ꮡh));   // was
return Ꮡh.Reinterpret<view>();                        // is
```

The old form is **not merely indirect, it is unsound**. golib's `implicit operator uintptr(ж<T>)`
ends in `fixed (void* ptr = &value.Value) return (uintptr)ptr;` — `fixed` pins only for the duration
of its own statement. The address escapes it, and `(ж<U>)(uintptr)` then builds a *native-backed* box
holding no reference to the source. The derived pointer therefore neither keeps its pointee alive nor
survives the collector moving it. Consumed immediately it happens to work; **retained**, it dangles,
and the read comes back silently wrong once another allocation reuses the address.

That is exactly what `reflect` does — `canonType` caches the reinterpreted `rtype` for process
lifetime — so after enough heap churn `TypeOf(x).Kind()` began reporting `Invalid` mid-process, which
inverted `fmt.Sprint`'s "space only between two non-strings" rule corpus-wide. See
[`docs/phase4/FINDING-managed-box-uintptr-lifetime.md`](phase4/FINDING-managed-box-uintptr-lifetime.md).

The golib extension `Reinterpret<T, TDst>()` decides by **provenance first**:

| Source pointer | Result |
|---|---|
| A nil box, or a plain `null` reference (both are Go's nil pointer) | `ж<TDst>.NilBox` — Go's `(*U)(unsafe.Pointer((*T)(nil))) == nil` |
| Aliases a NATIVE address (`m_nativeAddr` — a Win32 API's returned block) | the same address; the interop contract above is untouched |
| Owns MANAGED storage, and the reinterpret is representable (below) | a box aliasing that storage, through ж's existing struct-field-ref kind |
| Anything else | the pre-existing address route |

The managed arm recomputes its `ref` from a live object reference on every access
(`Unsafe.As<T, TDst>(ref …ValueSlot)`), so it is GC-safe and needs no pin. It composes through the
field-ref and array-element reference kinds — a reinterpret of `&s.f` or `&a[i]` aliases the real
storage rather than a copy — and two reinterprets of one box compare equal, as Go requires (ж
equality compares the source object plus the accessor, and the accessor is a static method).

It is an **extension method on `ж<T>?`**, which is what lets a `null` source be tolerated at all: a
zero-valued pointer field is a plain `null`, and an instance call on it throws where Go yields nil.
That is why the emission carries both type arguments.

**Why the managed arm is gated.** Go's rule for `(*TDst)(unsafe.Pointer(p))` is that `TDst` is no
larger than `T` and the two share an equivalent layout — but that rule cannot be inherited, because a
go2cs surrogate's C# layout is not its Go layout: a Go `[2]byte` is 2 bytes while `array<byte>` is a
single reference to a backing store, a Go `string` is 16 bytes while `@string` is 8, a Go `[]byte` is
24 while `slice<byte>` is 32. A **valid** Go reinterpret can therefore become an oversized
`Unsafe.As` that reads past the value slot into the box's own private fields and materializes a
*fabricated managed reference* — a CLR type-safety break, strictly worse than the contained
wrong-read the address route gives. So the alias is taken only where it is demonstrably safe: both
pointees value types; the destination fits inside the source; and either neither type contains
managed references, or the two are layout-compatible in the senses the converter generates (the same
type, a single-field wrapper over the other — Go's struct-embedding idiom and the generated
named-type wrappers — or an identical recursive field-type sequence). Everything else falls back to
the address route, so the change is additive: where it does not apply, behavior is exactly what it
was.

This mirrors **Go's own rule** on the other axis too: a pointer obtained through `unsafe.Pointer` is a
real reference the collector tracks, while a `uintptr` is a number that does not keep its referent
alive — so an arithmetic-derived source (`(*U)(unsafe.Pointer(uintptr(p) + off))`) keeps the address
route.

What this deliberately does **not** cover: Go's prefix-downcast idiom, where the runtime allocates a
larger struct, hands out a pointer to its embedded header, and casts back
(`(*structType)(unsafe.Pointer(t))` with `t` a `*abi.Type`). In Go the larger allocation is really
there; in the managed model a `ж<abi.Type>` holds only an `abi.Type`, so there is nothing behind it
to downcast to. Those sites keep the address route and remain the raw-metal class — which is why the
two of them that converted code actually *reaches*, `abi.Type.StructType()` and `ArrayType()`, are
hand-owned and SYNTHESIZED instead (see
[*`abi.Type`'s SPECIALIZATIONS are synthesized, not downcast*](#abitypes-specializations-are-synthesized-not-downcast--structtype--arraytype)).

Emission detail: the peeling is shared with the identity-reinterpret elision
(`pointerConversionSource` — it unwraps an optional `abi.NoEscape`/`noescape` wrapper and an
`unsafe.Pointer(p)` *conversion*, but never a function that merely returns `unsafe.Pointer`, e.g.
`mallocgc`). Identical element types stay the identity elision described above; differing element
types are the genuine reinterpret. The interception sits at the **two points that emit the address
route** — the conversion path and the regular-call path, since `(*U)(unsafe.Pointer(…))`
mis-classifies as a non-conversion and reaches only the latter — deliberately *not* upstream with the
identity elision: the re-box routes above render their own conversions correctly, and diverting them
breaks named ARRAY wrappers, whose lazily-materialized backing store a storage reinterpret bypasses.

**A pointer-to-ARRAY target is excluded at the CONVERTER, not at the golib gate.** For
`(*[N]T)(unsafe.Pointer(p))` the interception can only ever lose. golib never takes the managed arm
for it — `array<U>` is an 8-byte struct holding a backing-store *reference*, so it fails the size
gate against any smaller pointee and the reference gate against any numeric one, and `Reinterpret`
falls straight through to the address route it was meant to replace. But the address route's **text**
is not inert: the slice-of-pointer-cast fusion in `convSliceExpr` keys on a leading `(ж<…>`
(`isPointerCast`) to lower `(*[N]T)(ptr)[:n]` into a `slice<T>` over a `ReadOnlySpan<T>` of the
pointed-to memory — the only correct lowering of that idiom, since an `array<T>` can neither view
native memory nor be punned out of a scalar's bytes. Emitting `Reinterpret` defeats the match and
leaves `(~box).slice(…)` over an `array<T>` whose backing reference was read out of the **pointee's
data**: a fabricated managed reference, i.e. an `AccessViolationException` that kills the process
rather than the contained wrong read the address route gives. Measured end to end on
`internal/syscall/windows/registry.GetStringValue` — the read behind `time.initLocalFromTZI` and
`mime.initMimeWindows`, so essentially every Windows program that formats a local time or looks up a
MIME type: same probe, `Windows 10 Pro` before and a hard fault after. `pointerReinterpretManagedSource`
therefore returns nil for an array-underlying target, restoring the previous emission exactly, fused
or not. (`reparse_windows.path()` → `os.Readlink`, registry `Get`/`SetStringValue` ×4, `os/user`, and
`reflect.gcSlice` are on that route; 17 further corpus sites were already on the address route and
are unchanged.)

(Guarded by the `ReinterpretPointerLifetime` behavioral output test — an aliasing case, plus a
lifetime case whose reinterpreted pointer is the only surviving reference across heavy allocation
churn; before the fix it printed `lifetime: true false false` against Go's `true true true`. The
gate's fallback is guarded by `FixedArrayBufferPointer`, whose fixed-array pin must survive, and the
array-target exclusion by `PointerCastSliceReinterpret`, an output test over the NON-IDENTITY
pointer-cast slice that `PointerCastSliceRange` explicitly defers to "the stdlib exercises that
shape" — which is exactly how the fabrication reached the corpus untested.)

### A pointer-cast slice with a LOW bound offsets the span
`(*[N]T)(ptr)[lo:hi]` lowers to a `slice<T>` over a `ReadOnlySpan<T>` of the pointed-to memory (see
the fusion above). The span was always built from element 0 with length `hi`, dropping the low bound
entirely — so the result held the **wrong elements** whenever `lo` was non-nil, and was right only
when `lo` happened to be 0. Go's expression is the elements `lo..hi`, so the span must start at
element `lo` and run `hi - lo`:

```go
return syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(&rb.PathBuffer[0]))[n1:n2:n2])
```
```csharp
return syscall.UTF16ToString(new slice<uint16>(new ReadOnlySpan<uint16>(
    (uint16*)(uintptr)(new @unsafe.Pointer(Ꮡ(rb.PathBuffer[0]))) + (int)(n1), (int)(n2) - (int)(n1))));
```

A pointer cast binds tighter than `+`, so the offset lands on the typed pointer with no extra
parentheses. Two live consequences before the fix: `internal/syscall/windows`'s
`(*symbolicLinkReparseBuffer).path()` slices `[n1:n2:n2]` to skip the print name, so `os.Readlink`
returned the reparse buffer from offset 0 instead of the substitute name; and `internal/abi`'s
`FuncType.OutSlice()` slices `[InCount : InCount+outCount]`, so it returned the **in**-parameters
followed by the out-parameters and `reflect.Type.Out(i)` indexed the wrong half (`reflect.gcSlice`
`[begin:end:end]` likewise read the GC bitmap from the wrong start). (Guarded by the non-zero-low
arms of `PointerCastSliceReinterpret` — the reparse `[n1:n2:n2]` shape and a byte reinterpret of a
wider element sliced `[3:7]`, both wrong before and matching Go now. `StdLibInternalAbi`'s golden
re-baselines to the corrected `OutSlice`; its own stdout does not depend on the offset, which is why
it stayed green through the defect.)

### `unsafe.Pointer(p)` on a pointer PARAMETER renders the box, never a deref
A pointer parameter is emitted as the box `ж<T> Ꮡp` plus a deref'd VALUE alias
(`ref var p = ref Ꮡp.Value`). Taking its address through that alias —
`@unsafe.Pointer.FromRef(ref p)` — forces the alias to be materialized, so the entry-time deref
raises a nil-pointer panic for a **nil** argument, even though Go never touches the pointee and
`uintptr(unsafe.Pointer(nil))` is defined to be `0`.

Nil out-pointers are idiomatic in the syscall wrappers. `DuplicateHandle` takes `lpTargetHandle
*Handle` as nil together with `DUPLICATE_CLOSE_SOURCE` to close a handle *without* receiving a
duplicate, and `syscall.StartProcess` does exactly that in a deferred call — so spawning any child
process panicked there.

The address now comes from the **box**: `new @unsafe.Pointer(Ꮡp)`. golib's
`implicit operator uintptr(ж<T>)` already yields precisely the address Go wants — `0` for a nil box,
the aliased address for a native pointer (above), the pinned storage otherwise. Rendering the box
also removes the bare value reference from the body, so an otherwise-unused alias is dropped as dead
by the scan described under *A pointer parameter used only through its box gets no deref VALUE alias*
and the entry deref disappears with it. Parameters whose pointee is a basic or struct type already
took this form; a named-numeric pointee (`*Handle`) and a pointer-to-pointer (`**uint16`) did not.

A pointer **receiver** keeps the `FromRef` form — a `this ref T` receiver has no box to address.
Guarded by `NilPointerParamUnsafePointer` (nil and non-nil arguments across all three pointee shapes,
plus a case where the value alias stays genuinely live so the box rendering must not drop it).

## Implicit Pointer Dereferencing
**Deciding whether a selector base is *already* dereferenced.** A field selector on a pointer-valued base auto-derefs in Go, so the converter must insert the deref (`(~x).field` / `x.Value.field`) — *unless* the base is itself an explicit dereference (`(*p).field`) or a pointer conversion whose dedicated branch appends its own `.Value`. That "is the base already deref'd" test was a whole-subtree scan for **any** `StarExpr`, which mistook a conversion star buried in a call **argument** for a dereferenced base — `stringStructOf((*string)(unsafe.Pointer(p))).n` (runtime `arena.go`): the `(*string)` star belongs to the argument's conversion, the *call result* (`ж<stringStruct>`) is not deref'd, and skipping the auto-deref left `.n` on the box (CS1061). The test now inspects only the base's own outermost shape (unwrapping parens; a pointer-conversion base still routes to the conversion branch), and the conversion-branch dispatch also unwraps **enclosing parens**, so an extra-paren conversion base — `((*specialWeakHandle)(unsafe.Pointer(…))).handle` (runtime `mheap.go`) — reaches it (the same extra-paren blind spot the reinterpret routing had). Reads through a conversion base are faithful; a **write** through one hits the copy box, the documented reinterpret-seam limitation shared by the whole `(ж<T>)(uintptr)` family (the runtime sites are reads). The corpus was byte-identical across all behavioral projects after the change — only previously-non-compiling shapes gained emissions. (Guarded by the `PointerSelectorDeref` behavioral test — both shapes, read values vs Go; cleared 3 runtime CS1061, 74 → 71.)

In Go, pointer types automatically dereference; these `age` assignments are equivalent:
```go
var s struct{ age int }
var ps = &s
(*ps).age = 20
ps.age = 20
```
This also applies to receiver methods — a value-receiver method works on the type *and* on a pointer to it. In practice, the converter handles implicit dereferencing of a pointer parameter by binding a `ref` local to the box's value. For example:
```go
func PrintValPtr(ptr *int) {
    fmt.Printf("Value available at *ptr = %d\n", *ptr)
    *ptr++
}
```
becomes:
```csharp
public static void PrintValPtr(ж<nint> Ꮡptr) {
    ref var ptr = ref Ꮡptr.Value;

    fmt.Printf("Value available at *ptr = %d\n"u8, ptr);
    ptr++;
}
```

A pointer **local** that holds a `ж<T>` box (e.g. `x := list.head`, where `head` is a `*node`) dereferences on field access through the box — a read becomes `(~x).field` and a write `x.Value.field = …`. This applies to **promoted** fields too: when `T` embeds another struct, a selector naming an embedded field (`x.next` where `next` is promoted from an embedded header) must still dereference. The converter decides this by checking field membership recursively through embeds, so a promoted-field access on a pointer local is not left as a bare `x.next` on the box (which has no such member, CS1061). This mirrors the Go runtime's `scanstack`, which walks `x := state.head; … x.nobj` where `nobj` is promoted into `stackObjectBuf` from an embedded header.

When the field access is the **LHS of an assignment** and the chain is *nested* — `o.stack.hi = …` where `o` is a pointer local and `stack` is a value-struct field — every dereference in the base must use the assignable `.Value` form, not `~`: `(~o).stack` yields a value (an rvalue), so assigning to a field through it is not a variable/property (CS0131). The converter propagates the assignment context down the selector chain, emitting `o.Value.stack.hi = …`. This mirrors runtime/cgocall.go's `g0.stack.hi = sp + 1024` where `g0` is a `*g` local.

The same applies to **`++`/`--`** on a field reached through a pointer local — increment/decrement reads *and* writes its operand, so `(~mp).ncgocall++` (a field of an rvalue) is CS1059. The converter emits the assignable `mp.Value.ncgocall++`.

**An INDEX-expression assignment target takes the same `.Value` write path.** When the assignment LHS
is an index over a field reached through a pointer local — net/http client.go's redirect loop
`req.Header[k] = vv`, `Header` a named-map wrapper field — the read form `(~req).Header[k] = vv`
indexer-sets a wrapper-STRUCT field of an rvalue copy (CS0131; httptest's recorder hit the same shape).
The assignment threads a dedicated `IndexExprContext.isAssignmentTarget` flag (distinct from the
general assignment context, which also rides along RHS conversions where an index READ must keep the
deref form), and `convIndexExpr` converts its BASE in assignment context: `req.Value.Header[k] = vv;`.
Compound assignment (`m[k] += 2`), `++`/`--` on an index operand (`m[k]++` via `visitIncDecStmt`), and
tuple-deconstruction elements (`(config.Value.Certificates[0], err) = …`, net/http server.cs) take the
same path. The write form is emitted for EVERY boxed index-assignment base (75 stdlib files) — for
reference-backed fields (plain maps, slices, golib arrays) both forms compile and write through the
same backing store, so this is churn-free semantically; the named-wrapper fields are the shapes that
did not compile. (Guarded by `BoxedMapFieldWrite` — named-map-wrapper writes, plain-map writes,
compound/inc-dec writes, and an array-field element write, all through a pointer local, values
output-compared vs Go.)

**Dereferencing a pointer FIELD reached through a parameter — `*p.field`.** A `*p` where `p` is a pointer parameter is emitted as the value alias `p` itself (the `ref var p = ref Ꮡp.Value` local already denotes the pointed-to value), so the converter has a parameter-deref shortcut. That shortcut must fire only when the operand *is* the parameter (`*p`, or `**p`): for `*p.field` — a deref of a pointer *field* reached through `p` (`*gp.ancestors`, where `ancestors` is a `*[]ancestorInfo`) — the operand `p.field` is a distinct lvalue that still needs its own dereference. The shortcut keyed off the *root* identifier (`getIdentifier` digs through the selector to `p`), so it wrongly dropped the field deref, emitting `gp.ancestors` (the `ж<…>` pointer) instead of `gp.ancestors.Value`. That silently fed a pointer where the pointed-to value was expected — `for _, a := range *gp.ancestors` ranged the box (CS8130, since a `ж<slice<…>>` is not enumerable as tuples), and `x := *p.cnt` typed a pointer as a value (CS0029). The shortcut now excludes a **selector** operand, so `*p.field` falls through to the selector-deref path and renders `p.field.Value`. (Guarded by the `DerefPointerToField` behavioral test — a `for _, x := range *h.xs` over a deref'd pointer-to-slice field and a `*h.cnt` value read, both through a pointer parameter; runtime hit this on `traceback.go`'s `range *gp.ancestors`.) An **index** operand rooted at a parameter (`*temps[depth]`, math/big's slice-of-pointers element deref) is excluded the same way.

The **receiver** flavor of the same shortcut (`*u` inside `func (u *unifier)` → the deref-aliased `u`) had the identical overreach: it keyed off the root identifier, so `*u.handles[x]` — a deref of a **pointer-valued map element** reached through the receiver (go/types unify.go's `return *u.handles[x]` and `*u.handles[x] = t`, `handles` a `map[*TypeParam]*Type`) — dropped the element deref entirely, returning/assigning the raw `ж<ΔType>` (CS0266 in both directions). The receiver shortcut is now gated on the operand *being* the receiver ident (object identity, like every other receiver-specific render), and the non-direct operand falls through to the tail deref: `u.handles[Ꮡx].ValueSlot` (`ValueSlot` because the element's pointee is an interface — reference-like reads and writes both persist through the real slot). (Guarded by the `RecvMapElementDeref` behavioral test — element deref read and write through the receiver, the write observed through the shared pointer, alongside the genuine `return *r` receiver copy, output-compared vs Go.)

## Labeled Control Flow and Loop Variables
Go restricts a label to immediately precede the enclosing statement (e.g. a `for`). Equivalent behavior is produced with a placed label and a `goto`:

### Break Label
```go
OuterLoop:
    for i = 0; i < n; i++ {
        for j = 0; j < m; j++ {
            switch a[i][j] {
            case nil:
                state = Error
                break OuterLoop
            }
        }
    }
```
becomes (the label is emitted as `break_OuterLoop:`):
```csharp
    for (i = 0; i < n; i++) {
        for (j = 0; j < m; j++) {
            switch (a[i][j].type()) {
            case nil:
                state = Error;
                goto break_OuterLoop;
            }
        }
    }
break_OuterLoop:;
```

### Continue Label
```go
RowLoop:
    for y, row := range rows {
        for x, data := range row {
            if data == endOfRow {
                continue RowLoop
            }
            row[x] = data + bias(x, y)
        }
    }
```
becomes (`continue_RowLoop:` placed at the end of the *labeled* loop's body — so `goto continue_RowLoop` from the inner loop lands there and the **outer** loop proceeds to its next iteration; `break_RowLoop:` would go after the outer loop):
```csharp
    foreach (var (y, row) in rows) {
        foreach (var (x, data) in row) {
            if (data == endOfRow) {
                goto continue_RowLoop;
            }
            row[x] = data + bias(x, y);
        }
continue_RowLoop:;
    }
```
Both the `break_<label>`/`continue_<label>` labels are emitted for a labeled `for` **and** a labeled `range`/`foreach` loop (the label target is placed regardless of loop kind; a missing one is CS0159 "no such label"). Guarded by the `ForVariants` behavioral test (labeled `range` with nested `continue`/`break`).

### A user label on an empty statement emits an explicit empty statement

A Go label can attach to an **empty statement** — `keep:` as the last line of a block, a
`goto`/`break`/`continue` target with nothing between the label and the closing brace
(internal/trace gc.go's `goto keep` target at the tail of a for-loop body). A C# label must
precede a statement, so a bare `keep:` before `}` is CS1525/CS1002. `visitLabeledStmt` detects
an `*ast.EmptyStmt` target and emits the explicit empty statement `keep:;` — the same shape the
`break_<label>:;`/`continue_<label>:;` synthesis already uses. A label on a non-empty statement
is unchanged (`big:` followed by its statement stays bare). Guarded by `LabeledEmptyStmt` (a
`goto` to an end-of-loop-body label, a `goto` to an end-of-function label, and a `goto` to an
end-of-inner-block label — values vs Go).

### Reassigned or ref-bound range variable
A C# `foreach` iteration variable is **read-only**, but Go lets a `range` key/value variable be reassigned inside the body (it is a per-iteration copy). When the converter detects such a reassignment (`=`, `+=`, `-=`, `++`, …) of a newly-`:=`-defined range variable — or a **pointer-receiver method selected on the value-typed range var** (`q.GoString()`, whose emitted `[GoRecv]` form takes `this ref T`; a foreach var cannot bind `ref` — CS1657, dnsmessage's four `Message.GoString` loops) — it iterates a temp and declares the variable as a mutable local copy in the body, rather than binding it directly. The per-iteration `var q = vᴛ1;` copy preserves Go's semantics exactly: the pointer-receiver mutates the copy, as Go's implicit `(&q)` on the range copy does. A pointer-*typed* range var is excluded (it dereferences; no ref bind), as are value-receiver and interface-method selections. The machinery covers string AND slice/array/map ranges:

```go
for _, r := range s {  // r is a rune
    if r >= 0x10000 {
        r -= 0x10000   // reassigns the range variable — CS1656 on a foreach var
        …
    }
}
```
```csharp
foreach (var (_, rᴛ1) in s) {
    var r = rᴛ1;       // mutable local copy
    if (r >= 65536) {
        r -= 65536;
        …
    }
}
```
A range variable that is only *read* keeps binding directly to the `foreach` tuple (no temp, no churn). This reuses the same temp-var/`innerPrefix` machinery as the `for k, v = range` (re-assign-into-existing-vars) form. (Guarded by the `RangeVarReassign` behavioral test; runtime hits this in `os_windows`'s UTF-16 surrogate-pair encoder.)

**Addressability flows from the path ROOT, not the immediate operand.** Each trigger above is
matched against the identifier at the root of a field/element access path (`rangeVarRootIdent`),
because Go's addressability propagates through every value hop: `test.x.String()` with a
pointer-receiver `String` is legal in Go (it auto-takes `&test.x`), and C# names the same rule in
its diagnostics — CS1654/CS1655 speak of *"fields of"* the iteration variable. Matching only the
immediate operand missed the whole class, which matters far beyond one construct because this is
the **table-driven test** idiom that dominates the standard library's own test suites:

```go
for _, test := range tests {
    fmt.Println(test.x.String())   // pointer receiver on a FIELD of the range var — CS1655
}
```
```csharp
foreach (var (_, vᴛ1) in tests) {
    var test = vᴛ1;                // mutable, addressable per-iteration copy
    fmt.Println(test.x.String());
}
```

The same root walk covers a write through a nested path (`test.x.n = 99`, CS1654) and an address
taken of one (`&test.x`). The walk **stops at the first pointer hop**: past a pointer the access
goes through the heap box (`ж<T>.Value`), an independent mutable lvalue the iteration variable's
read-only-ness never reaches — so `test.ptr.Bump()` keeps the direct `foreach` binding with no copy,
and (matching Go) its write *is* observed by the source. Indexing likewise only continues through an
**array**, whose storage is in-place; a slice or map index reaches a separate backing store and is
its own addressability root. The copy is per-iteration, matching Go 1.22+ loop-variable semantics
(see the for-clause section below).

Scoping the trigger to the root this way *removes* work as well as adding it: the previous
field-write arm never checked whether the base was a POINTER, so a write like `s.Name = x` through a
pointer-typed range variable emitted a needless per-iteration copy. Across the 302-package
converted-stdlib corpus the net effect is **54 spurious copies eliminated and none added** — the
addressability additions show up in *test* code (the table-driven idiom), not in production sources.

### For-clause variables are per-iteration (Go 1.22 loop-variable semantics)

Go 1.22 gives each iteration of a `for i := …; cond; post` loop its **own copy** of the clause-declared variables, initialized from the previous iteration's final value. A C# for-clause variable is ONE variable shared by every iteration, so a closure captured in the body would observe the shared post-mutated final value (`3 3 3` instead of Go's `0 1 2`), and a stored `&i` would alias one shared box — compiling code that is silently wrong. When a clause-declared variable is **captured by a func literal in the body** or is **heap-boxed**, the converter rewrites the clause to drive a renamed *carrier* (`iᴛ1`) and re-declares the real variable fresh from the carrier at the top of the body; when the body can write the variable, its value is copied back to the carrier at every transfer to the post clause — the end of the body, before an unlabeled `continue` (a C# `continue` skips the end of the body), and after the `continue_<label>:` target (which the copy-backs deliberately follow, so a `goto continue_<label>` flows through them):

```go
var fs []func() int
for i := 0; i < 3; i++ {
	fs = append(fs, func() int { return i })
}
fmt.Println(fs[0](), fs[1](), fs[2]())   // Go 1.22+: 0 1 2
```
```csharp
for (nint iᴛ1 = 0; iᴛ1 < 3; iᴛ1++) {
    var i = iᴛ1;               // fresh per-iteration variable — each closure captures its own
    fs = append(fs, () => i);
}
```

A variable the body (or a closure in it) *writes* adds the copy-backs:

```csharp
for (nint iᴛ1 = 0; iᴛ1 < 6; iᴛ1++) {
    var i = iᴛ1;
    if (i % 2 == 0) {
        iᴛ1 = i;               // an unlabeled continue copies back at its own site
        continue;
    }
    i++;
    fs = append(fs, () => i);
    iᴛ1 = i;                   // end-of-body copy-back feeds the post clause
}
```

A **heap-boxed** clause variable allocates a *fresh box* each pass — the same rule as the [per-iteration range-variable box](#pointers): a stored `&i` must be a distinct pointer per iteration — and always copies back (writes through the pointer are not syntactically detectable):

```csharp
for (nint iᴛ1 = 0; iᴛ1 < 3; iᴛ1++) {
    ref var i = ref heap<nint>(out var Ꮡi);
    i = iᴛ1;
    ps = append(ps, Ꮡi);       // three DISTINCT pointers: 0 1 2
    iᴛ1 = i;
}
```

Loops whose clause variables are neither captured nor boxed emit exactly as before — the shared clause variable is then unobservable, so there is no churn. A read-only captured variable skips the copy-backs. Every clause reference (init/cond/post) renders the carrier, so the body keeps the variable's Go name — nested same-name loops compose with shadow renames (`iΔ1` gets carrier `iΔ1ᴛ1`), and a multi-variable clause transforms only the variables that need it. One legacy fallback: a heap-boxed variable that a **clause** func literal references keeps the old hoisted whole-loop box, since the body-scoped box would not be in scope at the clause. (Guarded by the `ForLoopPerIterationVars` behavioral test — read-only capture, body write + unlabeled `continue`, stored `&i` distinctness, boxed + captured closures, labeled continue with a write, nested same-name loops, a multi-variable clause, a struct-typed clause variable, and an immediately-invoked writing closure — values vs Go. `EscapedLoopVarSiblingIndex`, `ForVariants`, and `RingPointerMethods` re-baselined to the per-iteration shape.)

### A range-over-int index is `nint` (golib's `range` helper yields Go's `int`)
Go 1.22's `for i := range n` (range over an integer) produces `i` of type `int`, which go2cs maps to `nint`. The converter lowers it to a `foreach` over golib's `range` helper, and — with `-var` (the default) — leaves the index as the idiomatic `var`:
```go
size := 5
var s []int
for i := range size {
    s = append(s, i)
}
```
```csharp
nint size = 5;
slice<nint> s = default!;
foreach (var i in range(size)) {
    s = append(s, i);
}
```
For `var i` to infer `nint` — matching Go's index type — golib's `range(nint)` must *yield* `nint`, not a C# `int`. It originally returned `Enumerable.Range(0, (int)n)` (element type `int`), so `var i` inferred `int`. That is invisible until the index feeds a generic builtin: `append(s, i)` with `s` a `slice<nint>` and `i` an `int` matches **two** `builtin.append` overloads with **different** inferred `T` — `append<T>(slice<T>, params Span<T>)` infers `T=nint` (from `s`; the `int→nint` element conversion is implicit), while `append<T>(ISlice, params T[])` infers `T=int` (from `i`). Neither wins the argument-by-argument betterness tie (each is better on one argument), so the call is ambiguous — **CS0121**. An explicit `nint i` always resolved (both overloads then infer `T=nint`, and `slice<T>` beats `ISlice` on the first argument), which is the tell that the defect was the *element type* the index inferred, not the converter's `var` (correct and idiomatic) nor the `append` overload set (unambiguous for a correctly-typed `nint`). The root fix is therefore in golib: `range(nint)` yields `nint`. As a hand-written iterator (`for (nint i = 0; i < n; i++) yield return i;`) it also matches Go's integer-range semantics for `n <= 0` exactly (zero iterations), where the old `Enumerable.Range` threw on a negative count. Because the converter emission is unchanged, this is a pure golib change — byte-identical `check-no-regression` — that silently corrects the index type for *every* range-over-int loop in the corpus, and unblocked the `maps` and `slices` Phase-4 test suites (whose `want = append(want, i)` over a `range(size)` index hit exactly this CS0121). Guarded by the `RangeIntIndexAppend` behavioral test (the minimal `append(s, i)`-over-`range(size)` shape, output-compared vs Go; neutering golib's `range` back to `IEnumerable<int>` reproduces the CS0121).

### Range-over-integer covers EVERY integer type, and the iteration variable keeps the operand's width
Go 1.22's range-over-integer accepts **any** integer type, not just `int`, and gives the iteration variable *that* type. The converter recognized only `types.Int` and untyped-int, so every other integer kind — `uintptr`, `int64`, `uint8`, `rune`, a named integer type — fell through `visitRangeStmt`'s `unexpected 'ast.RangeStmt' expression` arm, which prints the statement as a **C# comment**. The loop did not fail to convert loudly; it silently *vanished*, and the program quietly computed a different answer.

The corpus site that proved it is `internal/abi`'s `for range atyp.Len` inside `unique.buildArrayCloneSeq` (`unique/clone.go`), where `atyp.Len` is a `uintptr`:
```go
for range atyp.Len {
	switch etyp.Kind() {
	case abi.String:
		seq.stringOffsets = append(seq.stringOffsets, offset)
	…
	}
	offset += etyp.Size()
}
```
```csharp
foreach (var _ᴛ1 in range<uintptr>((~atyp).Len)) {
    var exprᴛ1 = etyp.Kind();
    if (exprᴛ1 == abi.ΔString) {
        seq.stringOffsets = append(seq.stringOffsets, offset);
    }
    …
    offset += etyp.Size();
}
```
The whole body had been a `/* … */` block, so `unique`'s `cloneSeq` for any array-of-string type came back **empty** instead of carrying one offset per element. It was the only such comment in the entire converted standard library — a one-site defect precisely because non-`int` range operands are rare, and invisible for exactly the same reason.

Two pieces carry the fix. golib gains a generic `range<T>(T n)`, constrained to the **operator pair the loop actually uses** (`IComparisonOperators<T,T,bool>` + `IIncrementOperators<T>`, with `default(T)` for the zero) rather than `IBinaryInteger<T>` — golib's own `uintptr` is a hand-written struct that implements the generic-math *operator* interfaces but not the `INumberBase` hierarchy, and `uintptr` is exactly the type this site ranges over. The converter then names `T` **explicitly** at every non-`int` site (`range<uintptr>(…)`, `range<rune>(…)`), because Go's `rune` and C#'s `int` are one CLR type and an inferred call could not tell `for i := range r` (yields `rune`) from `for i := range 3` (yields Go's `int`); a NAMED integer type additionally has its operand cast down to the underlying width, since a converted `[GoType("num:…")]` struct satisfies no generic-math interface at all.

The `int` case is emitted byte-identically — bare `range(expr)` — but it needed one guard of its own. A literal operand hands the overload set a C# `int`, where the new generic is an **identity** match and `range(nint)` needs an implicit numeric conversion; the generic therefore wins outright and C#'s "a non-generic method is better than a generic method" tie-break never applies (measured, not reasoned — the first attempt did regress `range(3)` to `System.Int32`). golib carries a third overload, `range(int n) → IEnumerable<nint>`, meaning "a C# `int` operand is Go's `int`": an `int`-typed operand in emitted code can only be an untyped Go constant, since a Go `int` expression already renders as `nint` and every other width is emitted with an explicit type argument. With it, `for i := range 3` stays on `nint` and the CS0121 above cannot come back.

Guarded by the `RangeOverIntegerTypes` behavioral test (blank-key `uintptr`, `uint8`/`int64`/`uint64`/`rune`, a named `uintptr` type, non-positive operands, and the unchanged `int`/untyped cases, output-compared vs Go — reverting the `isInt` widening reproduces `FAIL [Target,Output]`) and by `GolibTests.GoStructLayoutTests` (the `range<T>` widths and the literal-operand overload binding). Behavioral `check-no-regression` is byte-identical across all 570 packages apart from the new project, which is the expected shape: the corpus contained exactly one non-`int` range operand.

### A blank scalar range variable never emits as `_`
A `range` with no iteration variable (or an explicit blank) over a **scalar-yield** source — a channel, an integer (Go 1.22 `for range n`), or a single-value yield function — needs a C# `foreach` iteration variable, and that variable must **not** be named `_`: in a scalar `foreach` position C# declares a genuine read-only variable *named* `_` (only tuple-deconstruction `_` is a discard), which shadows the discard idiom for the entire loop body. Any Go blank assignment inside the body (`_ = f(x)` — evaluate and discard) then resolves to that variable and becomes an illegal write to a `foreach` iteration variable (**CS1656**; first hit: `encoding/binary`'s `BenchmarkSize`, `for range b.N { _ = Size(data) }`). The converter emits a marked temp instead:
```go
for range b.N {
    _ = Size(data)
}
```
```csharp
foreach (var _ᴛ1 in range((~bΔ1).N)) {
    _ = Size(dataʗ1);
}
```
Tuple positions are unaffected — `foreach (var (_, data) in …)` keeps the true C# discard. Guarded by the `RangeStatements` behavioral test (blank int-range and blank channel-range, each with a body blank assignment; the compile phase is the guard — the old emission is CS1656).

### `for range` over a slice allocates NOTHING — `slice<T>.GetEnumerator()` returns a struct

`for i, v := range s` emits `foreach (var (i, v) in s)`, and Go's range over a slice allocates nothing at all. C# matches that only if the enumerator stays off the heap, which is entirely a question of what `GetEnumerator` **returns**: `foreach` binds `GetEnumerator` by **pattern** — the concrete return type, ahead of and independently of any interface — so a struct return is enumerated in place, while an interface return is a heap object per loop *entry*.

`slice<T>.GetEnumerator()` returned `IEnumerator<(nint, T)>` from an ITERATOR method (`yield return`), which is the worst of both: the compiler-generated state machine is one allocation and the inner `SliceEnumerator` class it drove is a second. Measured at **136 bytes per loop entry**, corpus-wide — every ranged loop in every converted package, paid whether the loop body allocated or not. It is invisible in output and in timings at small scale, and unmissable in a Go test that asserts an allocation count: `time.TestUnmarshalTextAllocations` runs `parseRFC3339`, whose `parseUint` closure ranges its argument once per field.

The return type is now the concrete nested `slice<T>.Enumerator` struct (the shape `List<T>.Enumerator` uses, and the one golib's own `sslice<T>` already had). Two contracts had to move with it:

* `slice<T>` reaches `IEnumerable<(nint, T)>` through `ISlice<T>` → `IArray<T>`, which the old public method satisfied implicitly. The interface member is now an **explicit** implementation returning the same struct boxed — so LINQ, an interface-typed local, and anything holding the slice as `IEnumerable<(nint, T)>` behave exactly as before, at exactly the cost they already paid. Only the pattern path is free.
* go2cs-gen's `ISliceTypeTemplate` (every `type S []E` named-slice wrapper) forwarded the interface. It now forwards `global::go.slice<E>.Enumerator` and carries the same explicit interface member, so a named slice type ranges as cheaply as the `slice<E>` it wraps — otherwise every `for range` over a named slice would have kept the box.

`array<T>.GetEnumerator()` is the identical shape and is deliberately **not** changed here: Go's `range` over an array value ranges a COPY, so the eager-vs-lazy capture point is a semantic question there rather than a purely mechanical one, and it wants its own measured change.

Guarded by `SliceRangeAllocationTests` in `GolibTests`, which asserts **zero** bytes via `GC.GetAllocatedBytesForCurrentThread` across 1,000 loops (whole slice, sub-window with window-relative indices, and the nil slice), plus the interface-path equivalence. It is a measured guard on purpose: restoring the interface return type still compiles and still produces correct output — it just allocates again — so only bytes can catch the regression. Neutering to the interface return reports 48 B/loop; restoring the original iterator body reports exactly 136 B/loop.

## The `go.golib` support namespace

golib's hand-written support types (`SparseArray<T>`, `PinnedBuffer`, `TypeExtensions`, `HashCode`, `FatalError`, …) live in the **`go.golib`** child namespace — deliberately NOT `go.<any Go package name>`. The namespace was originally `go.runtime`, which collides with the real `runtime` package: converted code imports runtime as `using runtime = runtime_package;` inside `namespace go`, and a child namespace `go.runtime` visible from any referenced assembly (golib is referenced by *every* project) wins simple-name lookup over the alias — CS0576 at every `runtime.X` use (surfaced by `iter`/`internal/weak` in wave 1). The same reasoning forbids `go.internal`, `go.sync`, etc.; `golib` is not a Go stdlib package name, so the child namespace can never collide with an import alias. Emitted code references these types via the child namespace (`new golib.SparseArray<T>{…}`), which resolves inside `namespace go` with no using directive.

The general form of this collision — a REAL parent/child package pair — is handled by **Δ-renaming the import alias**. A C# using alias declared inside a namespace conflicts with a same-named child namespace visible from ANY transitively referenced assembly (CS0576 at every use), and transitivity makes this common: `runtime.csproj` itself references `runtime/internal/math|sys` (namespace `go.runtime.@internal`), so *every* package importing `runtime` sees a `go.runtime` child namespace — `iter` and `internal/weak` surfaced it in wave 1 (`weak`, in namespace `go.@internal`, collides with `go.@internal.runtime` from `internal/runtime/*` instead). A pre-pass computes the package's transitive Go import closure (exactly mirroring MSBuild's transitive ProjectReference visibility), derives every child-namespace chain it contributes, and Δ-renames any import alias the current package's namespace would capture: `using Δruntime = runtime_package;` with uses `Δruntime.Goexit()` — the established collision marker. The rename propagates through one lookup to the using emission, package-qualifier identifiers, and cross-package type-name prefixes; a package with no collision emits byte-identically. (The behavioral corpus sees this on `io` — the real Go closure contains `os → io/fs`, hence `go.io` — captured in the `AnonymousInterfaces` golden as `Δio`.)

Three properties of the rename, established empirically (2026-07-16 review of the `Δmath` emissions; ruled working-as-designed): **(1) The trigger is per-file and, for a top-level parent package, fires only in packages emitting into `namespace go`.** The collision key is `<packageNS>.<alias>`, and usings are per-file — so `math` (whose own closure always contains `math/bits`, hence `go.math`) renders as `Δmath` in exactly the `namespace go` importers' math-importing files (strconv's ftoa/atof/eisel_lemire, fmt's scan, reflect's value, expvar, testing's benchmark), while every nested-namespace importer (`go.compress`, `go.crypto`, …) keeps the clean `using math = math_package;`: an alias declared inside the file-scoped nested namespace wins simple-name lookup before the outer `go.math` is consulted. That inner-scope exemption is why the clean form dominates the corpus. **(2) The baseline stub is lenient only by omission.** The clean alias compiles against `src/core` solely because the hand-owned stub csprojs omit the Go closure (`core/math` references just golib); in the design-target consumption — full conversion, NuGet packages, `-recurse` apps, all with transitive reference visibility — the clean alias is CS0576 at every use, and hoisting it to compilation-unit scope merely trades that for CS0234 (inside `namespace go` the child namespace shadows the alias). Output must be context-independent, so the conservative rename stands. **(3) The marker cannot be swapped onto "the colliding item".** That item is the child namespace itself — the import-path-mirroring namespace of `math/bits` et al., baked into separately-compiled referenced assemblies — so there is nothing local to rename, and renaming the namespace would break the path-mirroring invariant corpus-wide (the mechanism covers `Δruntime` ×48 files, `Δsync` ×38, `Δio` ×23, `Δsyscall`, `Δunicode`, …). The `MathFloatBits` and `GoNamespaceShadow` goldens pin the `Δmath` form.

### Foreign renamed types reference the recorded imported-type alias
A cross-package type that is renamed (or Go-aliased) inside its own package -- `syscall` declares `ΔHandle` for its type-vs-method-colliding `Handle` -- must be referenced through the recorded imported-type alias (`global using syscallꓸHandle = go.syscall_package.ΔHandle`): the raw qualified render (`Δsyscall.Handle`) names a type that does not exist (CS0426 x26, internal/poll). The substitution lives at the C#-NAME layers -- `getCSharpTypeName` (delegate elements, parameters, results) and `getScopeCheckedTypeName` (named struct fields) -- and deliberately NOT in `getAliasQualifiedTypeName`: the Go-shaped name layer also feeds promoted-embed MEMBER naming, where the substitution renamed and rescoped the generated accessors (reflect CS8799 regression on the first cut). The GoImplicitConv assembly attributes record type names under the file-local import qualifier, so the resolving `using` in package_info.cs declares that same qualifier (`using Δsyscall = go.syscall_package;`).

A **pointer/box (or other composite) element** — `*time.Location` as a func result (archive/zip's `timeZone`), a `*syscall.Handle` parameter, a slice/map element — is renamed too, but by a *different route* that does not need `getAliasQualifiedTypeName` (so the CS8799 landmine is untouched): `getAliasQualifiedTypeName` renders the Go-shaped `*time.Location` (unrenamed, per above), then the downstream `convertToCSFullTypeName` applies `getAliasedTypeName` to the FINAL string identifier — substituting `time.Location → timeꓸLocation` before boxing — yielding `ж<timeꓸLocation>`. So the alias reaches every position that flows through the C# type-name conversion (values, pointers, boxes, composite elements alike), **provided `importedTypeAliases` is populated**. That map is loaded from the imported package's `package_info.cs` (the `[GoTypeAlias]` round-trip), so a *fresh full reconvert* renders the alias everywhere; a **stale/partial overlay** that lacks the up-to-date `package_info.cs` renders the raw name and mis-reports CS0426 — the failure is in the measurement tree, not the converter (internal/trace/testtrace's `trace.Time`/`Event`/`Stack` and archive/zip's `*time.Location` were both bank-diagnosed as converter roots, then shown by a clean reconvert to already render `traceꓸTime`/`ж<timeꓸLocation>`).

The map is now populated for the WHOLE package before any file converts. Even within a fresh reconvert,
`importedTypeAliases` was loaded INCREMENTALLY — `visitImportSpec` loads a package's aliases only when it
visits an import of that package, and files convert in sorted-filename order. So a foreign renamed type
reached TRANSITIVELY — through a value whose package the current FILE does not itself import — rendered its
raw (nonexistent) name if that file converted before any file that DOES import the package. go/printer's
`comment.go` (`slash := list[0].Slash`, a `token.Pos` read through `ast.Comment`, importing only `go/ast`)
sorts first, so its `slash` heap box emitted `heap<go.token_package.Pos>` instead of `heap<tokenꓸPos>`
(= `go.go.token_package.ΔPos`) — CS0426, the sole such site in the stdlib. A package-level pre-pass
(`preloadImportedTypeAliases`, run before the file-conversion loop) now loads the exported aliases of every
package ANY file imports, up front. The load is deduped per imported package, so it only FRONT-LOADS what
`visitImportSpec` did incrementally; the alias set is file-order-independent and, because it only ADDS
aliases previously missing for a transitive-use file, it can only turn a currently-WRONG render right (a
compiling package has no wrong-rendered renamed type) — CNR byte-identical across the behavioral corpus, and
an A/B full-stdlib reconvert changes exactly one file (go/printer/comment.cs), greening go.printer alongside
the append-disambiguation root above. (Guarded by the three-package `TransitiveAliasPreload` fixture:
`CrossPkgBox.Box` carries a field of `CrossPkgLib`'s Δ-renamed `Status`; the test's `a_boxed.go` (sorts
first) reads it transitively — `return &s` heap-boxes `s`, rendering `heap<CrossPkgLibꓸStatus>` — while
importing only `CrossPkgBox`, and `z_main.go` (sorts last) is the only file importing `CrossPkgLib`. Without
the preload the box renders the nonexistent `CrossPkgLib_package.Status` (CS0426); output-compared vs Go, 4
phases green. This is the three-package shape the 2-package `CrossPkg` harness could not previously express —
cf. the `os.FileInfo` alias root, still GUARD OWED above for that reason.)

The preload still covers only packages **some file imports**. A foreign renamed type reached ONLY through
ANOTHER package's signature — go/types renders go/ast's `FieldFilter` (`func(string, reflect.Value) bool`)
when it passes `ast.NotNilFilter` to `ast.Fprint`, and **no go/types file imports `reflect`** — had no alias
loaded at all, so the synthesized delegate wrap rendered the raw name: `new Func<@string, reflect.Value,
bool>(ast.NotNilFilter)` — `Value` resolved inside `reflect_package` (CS0426) and the mismatched delegate
then failed the method-group conversion (CS0123). `aliasedElementTypeName` (the delegate-element rename
route) now loads the owning package's exported aliases **on demand** when a foreign named element has no
registered alias — `loadImportedTypeAliases` is deduped per package, so a miss costs one probe — and the
resolving `global using reflectꓸValue = go.reflect_package.ΔValue;` rides the normal package_info emission
(the consumer sees the type through its importer's **transitive** assembly reference). For LOCAL modules the
resolver map (`importPackageDirs`) is now captured over the **transitive** import closure rather than direct
imports only, so the same on-demand load works outside GOROOT. (Guarded by `SynthesizedDelegateCrossPkg`:
`CrossPkgFuncLib.Picker func(CrossPkgLib.Status) bool` + exported `Hot` matching it; the consumer imports
only `CrossPkgFuncLib` and passes `Hot` where a `Picker` is expected — the wrap must render
`new Func<CrossPkgLibꓸStatus, bool>(CrossPkgFuncLib.Hot)`; output-compared vs Go, 4 phases green.)
```csharp
public static Func<CrossPkgLibꓸStatus, nint> CheckFunc = (CrossPkgLibꓸStatus st) => st.Code * 2;
internal static (CrossPkgLibꓸStatus, nint) gauge(CrossPkgLibꓸStatus st) {
internal static ж<CrossPkgLibꓸStatus> statusPtr(ж<CrossPkgLibꓸStatus> Ꮡst) {  // *Status → box of the alias
```
Guarded by `CrossPkgUser` (`CheckFunc`/`gauge`/`meterBox` -- delegate, signature, and field positions; `statusPtr`/`ledger` -- a `*CrossPkgLib.Status` pointer as a func parameter, result, and struct field, each boxed as `ж<CrossPkgLibꓸStatus>`).

### A foreign package's collision rename is derived from that package, not from the conversion run

Everything above depends on `importedTypeAliases` being **populated**, and the only source it had was the
dependency's emitted `package_info.cs` — an artifact that exists only once that dependency has been
converted **into the output root this run resolves against**. So the spelling of a foreign renamed member
depended on the *composition of the run*: a full `-stdlib` run converts `time` before `archive/tar`, so
`writer.cs` correctly emitted `tw.hdr.ModTime.Round(time.ΔSecond)`; converting `archive/tar` **alone**
(`go2cs -stdlib archive/tar`) emitted the unrenamed `time.Second`, which binds the `Second(this Time)`
extension method group — CS0019/CS1503/CS0023, it does not compile. That hit **every end-user path**, where
the stdlib is by definition *not* part of the run: a standalone `go2cs <dir>` and `-recurse` alike. `time`
is the worst case (`Second`/`Minute`/`Hour`/`Nanosecond`/`UTC`/`Local` all collide with `Time`'s accessors,
and `Location`/`Month`/`Weekday` are collision-renamed types), so the flagship four-line program
`d := 2 * time.Second` failed to compile.

**Invariant:** a foreign package's collision renames are a function of **that package's own declarations**,
never of which packages the current run converts. `foreignCollisionTypeAliases` derives them from the
dependency's loaded `go/types` scope — an exported package-level const/var/defined-type whose name is also a
method or function name of the same package, exactly `performNameCollisionAnalysis`'s rule — reproducing the
`GoTypeAlias` entries the dependency's own conversion publishes, and feeding them through the *same*
normalization as parsed ones (`applyExportedTypeAliases`) so a derived target is qualified identically. The
derivation runs only where the loader previously did nothing at all (no `package_info.cs` on disk), so a
conversion that *can* read the real artifact is untouched — the whole-stdlib emission is byte-for-byte
unchanged. This is the same discipline `packageHasMethodNamed` applies to the cross-package *field* rename
above: recompute a foreign package's collisions from its own `types.Package` rather than from run-accumulated
state.

Three shapes are reproduced, matching what the dependency's conversion publishes:

| Dependency declares | Published entry | Consumer emits |
|---|---|---|
| `const Second Duration` + `func (Time) Second() int` | `("Second", "const:ΔSecond")` | `time.ΔSecond` |
| `type Month int` + `func (Time) Month() Month` | `("Month", "ΔMonth")` | `timeꓸMonth` |
| `type Token any` + `func (*Decoder) Token() Token` | `("Token", "ΔToken")`, `("ΔToken", "object")` | `object` |

Two shapes are deliberately **not** derived, because publishing a wrong target is worse than publishing
none: a methodless named func type (rendered inline as its base delegate, so no `<pkg>_package.Δname` type
exists to alias — `go/doc`'s `ast.Filter`, the same skip `writePackageInfoFile` applies), and a defined type
over a **non-empty named interface**, whose alias target is a `visitTypeSpec`-only rendering of the RHS (no
instance exists in the 302-package corpus). Both keep the pre-existing emission.

A derived alias's `global using` is emitted into the consumer's `package_info.cs` **only when an emitted
reference resolved through it**. A parsed alias set describes an assembly that provably declares every
target; a derived set describes what go2cs *would* emit for that dependency's Go source — true of any real
conversion, but not of a hand-written proxy such as the baseline `core/time` stub, which declares no
`ΔLocation`/`ΔMonth`/`ΔWeekday` at all (an unused `global using` to one is CS0426 in every behavioral test
that imports `time`). Gating on use keeps the derived metadata's reach to the code that actually names the
renamed member — where the rename is required for the reference to bind at all — at the cost of a
single-package conversion omitting the *unused* alias declarations a full run emits. Every emitted
**reference** is identical either way: a single-package `-stdlib archive/tar` reconvert is byte-identical to
the committed full-run corpus in all code, and the flagship program compiles and runs.

Two neighboring classes of run-composition dependence share the loader and the symptom but are *not*
collision renames, so this derivation does not cover them: a dependency's re-exported Go type **aliases**
(closed next) and its **GoImplement** pairs (`loadPackageImplements`, still open — see the end of the next
subsection).

(Guarded by `foreignNameCollisions_test.go`: a two-package fixture whose `dep` carries one of every shape —
colliding const, colliding type, colliding empty-interface type, methodless func type, and a non-colliding
control — asserting the derivation, its independence from run-accumulated `nameCollisions` state, and the
end-to-end render (`dep.ΔSecond`, `depꓸMonth`) with no `package_info.cs` present.)

### A foreign package's re-exported type ALIAS is derived from that package too

The sibling class, and the one that hits real end-user code hardest. `os` declares
`type FileMode = fs.FileMode` (likewise `FileInfo`, `DirEntry`, `PathError`), and a re-export takes
`visitTypeSpec`'s **using-alias** arm: the converted `os` emits an assembly-scoped
`global using FileMode = go.io.fs_package.FileMode;` and publishes
`[assembly: GoTypeAlias("FileMode", "go.io.fs_package.FileMode")]`. The re-export is therefore a *using
alias inside os's assembly*, **never a member of `os_package`** — so a consumer converted without that
artifact emits `os.PathError` and gets `CS0426: the type name 'PathError' does not exist in the type
'os_package'`. Exactly the run-composition dependence of the collision renames, one metadata class over.

`foreignTypeAliases.go` derives these under the same invariant — what a dependency publishes is a function
of that package's own declarations — from its `go/types` scope, plus its syntax for the one distinction only
a declaration's RHS carries. Two declarations take the using-alias route and are reproduced:

| Dependency declares | Published entry | Consumer emits |
|---|---|---|
| `type FileMode = fs.FileMode` | `("FileMode", "go.io.fs_package.FileMode")` | `osꓸFileMode` |
| `type Kind = abi.Kind` (and `abi` Δ-renames `Kind`) | `("Kind", "go.@internal.abi_package.ΔKind")` | `reflectliteꓸKind` |
| `type PublicKey any` (a DEFINED type over the empty interface) | `("PublicKey", "object")` | `object` |
| `type Reader io.Reader` (a DEFINED type over a named interface) | `("Reader", "go.io_package.Reader")` | `pkgꓸReader` |

Three details make the reproduction exact rather than approximate:

* **The alias TARGET carries the target package's OWN collision rename.** `internal/reflectlite`'s
  `type Kind = abi.Kind` publishes `go.@internal.abi_package.ΔKind`, because `internal/abi` Δ-renames `Kind`
  against `(*Type).Kind()`. In a full run that Δ arrives from `abi`'s parsed `package_info.cs` (the
  `importedTypeAliases` consult in `convertToCSFullTypeName`'s default arm); the derivation recomputes it
  with the same foreign-package-aware `packageHasMethodNamed` test the collision lane uses, so the two
  sources agree.
* **A Go type ALIAS is exempt from the collision lane.** `performNameCollisionAnalysis` records only
  *defined* types (`!typeSpec.Assign.IsValid()`), so an alias name that *also* names a method is **not**
  Δ-renamed — `reflectlite` declares both `type Kind = abi.Kind` and `(*rtype).Kind()` and still publishes
  the plain source name `Kind`. The two derivations split on exactly that line: a colliding *defined* type
  belongs to the collision lane (which owns the `Token`/`ΔToken`/`object` two-hop), a colliding *alias* to
  this one. Getting that boundary wrong either double-publishes one source name with two targets or drops
  `reflectlite`'s entry entirely.
* **An empty-interface target is `object`, imported BARE.** `type PublicKey any` is not an alias at all but
  a defined type over the empty interface, which has exactly that interface's method set and so takes the
  using-alias arm too. Its target is the C# keyword, not a package member (`isCSharpBuiltinTypeName`) —
  `crypto`'s `PublicKey`/`PrivateKey`/`DecrypterOpts`, `plugin`'s `Symbol`, `database/sql/driver`'s `Value`.

**Deliberately not derived** (a wrong target is worse than none — a missing entry leaves the reference
exactly as it converts today, a wrong one names a type that does not exist): a composite or basic RHS
(`type Table = map[string]int`, whose rendering runs the whole `convertToCSFullTypeName` lowering) and an
alias-to-an-alias chain; an **anonymous** struct/interface RHS, which is *lifted* under a generated name
only a conversion assigns (`internal/fuzz`'s `type CorpusEntry = struct{…}` → `CorpusEntryᴛ1`); a generic
target; a methodless named **func** type, rendered inline as its base delegate with no named type to point at
(the same omission `typeCollisionAliases` and `writePackageInfoFile` make); and a target that is **itself**
emitted as a using alias by its own package, which would need a second hop this derivation does not follow.
Each declines by shape, from the dependency's own declarations, so the decision is stable across runs.

Same use-gating as the collision renames: a derived alias's `global using` reaches the consumer's
`package_info.cs` only once an emitted reference has resolved through it. Evidence, taken with the fix
neutered and restored: a standalone `os.FileMode`/`os.FileInfo`/`os.PathError` + `fs.WalkDir` program
converted with `go2cs <dir>` against an output root holding **no** converted stdlib failed with the CS0426
above and now compiles and runs with output byte-identical to `go run .`; single-package `-stdlib crypto/ecdh`
reconverts `ecdh.cs` byte-identically to the committed full-run corpus where before it emitted the
nonexistent `crypto.PublicKey`; and a whole-stdlib reconvert is byte-for-byte unchanged, the derivation
running only where the loader previously did nothing at all.

**Still open — the `GoImplement` pairs** (`loadPackageImplements`), and *honestly* so rather than pending:
they are recorded at CONVERSION time from the cast and witness sites a dependency's own bodies contain, so
which adapter classes its assembly actually carries is a product of its **emission**, not of its
declarations. There is nothing sound to compute from `go/types`: an over-approximation (every exported type
× every exported interface) would name adapters that do not exist — CS0246, strictly worse than the present
behavior, where the consumer records and emits its own local adapter (`io_SectionReaderжReader` instead of the
provider's `io.SectionReaderжReader`), which compiles and behaves identically and only duplicates the class.
The class retires with the runtime interface shells rather than with a derivation: once a concrete-to-interface
conversion goes through a runtime-constructed shell instead of a compile-time adapter, there is no per-pair
record left to be missing.

(Guarded by `foreignTypeAliases_test.go`: a three-package fixture — a consumer, the `dep` whose re-exports
are under test, and the `other` it re-exports from — carrying one declaration of every published shape and
every declined one, asserting the derivation, its independence from run-accumulated state, and the end-to-end
render with no `package_info.cs` present.)

### A DOT-IMPORTED renamed type is spelled through the same alias as the qualified reference

The two subsections above are about the alias metadata being **derived**; this one is about it being
**used**. Having the right alias minted is not the same as reaching it, and one reference path did not.

A dot import (`. "go/types"`) makes a foreign type's reference a bare `*ast.Ident` — there is no selector for
the qualified-name resolver to rewrite — yet the type may still be collision-renamed inside its own package.
The **type-driven** positions were always fine: a declaration, a parameter, a conversion and a field all
resolve from `types.Type` through `getCSharpTypeName`/`getScopeCheckedTypeName`, both of which consult
`foreignAliasedTypeName`. That is why `var mu Mutex` through a dot import has worked since
`DotImportRenamedPackage`. The two **AST-ident** type positions did not: a *type-assertion target* and a
*composite-literal type* render through `convIdent`'s `isType` arm, which returned the bare sanitized Go
name and consulted nothing.

So `internal/types/errors`, whose external test file dot-imports `go/types`, emitted `err._<Error>(ᐧ)` and
`new Info(…)` against declarations named `ΔError` and `ΔInfo` — `go/types` renames `Error` for its own
`func (err Error) Error() string` and `Info` for the unrelated `func (b *Basic) Info() BasicInfo` — while
that test's own `package_test_info.cs` had already minted `global using typesꓸError = …ΔError;` and
`typesꓸInfo`, and left both unused. CS0246 ×2.

**Invariant:** one Go type has one C# spelling, whatever the source called it. `convIdent`'s `isType` arm
now routes through `foreignAliasedTypeName` — the *same* recorded-alias lookup the qualified path takes — so
`Info{…}` and `types.Info{…}` emit the identical `typesꓸInfo`. It is a no-op for a same-package type and for
any type with no registered alias, so nothing else moves (whole-corpus CNR byte-identical).

```csharp
var m = new renamedlibꓸMarker(Name: "alpha"u8, Size: 3);          // composite literal  (was: new Marker(…))
var (got, ok) = Describe(deltaˢ, 9)._<renamedlibꓸMarker>(ᐧ);      // type assertion     (was: _<Marker>(ᐧ))
var pl = new Plain(Note: "eta"u8);                                 // NOT renamed — bare, unchanged
var l = new ΔLocal(Tag: "iota"u8);                                 // same-package rename — local, no alias
```

The rename rule itself is `performNameCollisionAnalysis`'s and is worth stating exactly, because the second
half is easy to miss: a package-level named element collides when **some** package-level `FuncDecl` in that
package shares its name. Both a method on the type itself (`Error`) and a method on an unrelated type
(`Info`) supply it; since Go forbids a type and a free function sharing a package-scope name, the collision
can only ever come from a method.

(Guarded by `DotImportRenamedType`: a sibling library package declaring one type of each collision shape
plus a non-renamed control, consumed across the package boundary through a dot import via composite
literals — value and pointer — and type assertions in comma-ok, single-value and missed forms, with a
same-package renamed type as the second control; output-compared vs `go run`. Verified to FAIL as CS0246
with the fix reverted.)

### Converted programs write UTF-8 stdout — the ambient console code page never reaches the bytes
Go writes stdout as raw UTF-8, unconditionally: `fmt.Println("Hello, 世界")` emits the same bytes to a
terminal, a pipe, or a file. .NET does not. `Console.Out` is constructed with `Console.OutputEncoding`,
which on Windows defaults to the **console output code page** (`GetConsoleOutputCP()` — the OEM page, 437
on a stock US install), and to the ANSI default (1252) when the process has no console at all — the case for a
program launched by the behavioral runner (`CreateNoWindow = true`) or by the tour's `.NET Run` pane
(`src/tour/pipeline.go` `runStage`, whose `command.Stdout` is a `bytes.Buffer`). Encoding a rune that code
page cannot represent is not an error in .NET; the encoder substitutes `?`, so the Tour of Go's first
lesson would render `Hello, ??` with no diagnostic anywhere.

golib forecloses this in its `[ModuleInitializer]` (`src/core/golib/builtin.cs`), which runs before any
converted code: `Console.OutputEncoding = Console.InputEncoding = Encoding.UTF8`. The setter also discards
any already-created `Console.Out`, so the writer is rebuilt on the UTF-8 encoding, and .NET strips the
encoding's preamble for console writers — no BOM is prepended. A failing `SetConsoleOutputCP` (no console
attached) is tolerated, so the redirected case is covered as fully as the interactive one. The **full**
conversion reaches the same place by a different route and needs nothing added: real `fmt` writes through
`os.Stdout` → `internal/poll` → `syscall.WriteFile`, which hands the `[]byte` to Win32 verbatim and is
byte-transparent by construction. Only the baseline `core/fmt` stub — a proxy over
`Console.Write`/`Console.WriteLine` — depends on the encoding above.

Guarded by the `UnicodeConsoleOutput` behavioral test, which prints CJK, Greek, Cyrillic, a math symbol and
an astral-plane emoji, and is stdout-compared against the Go binary. The guard is differential, so it holds
even under a lossy capture: the runner decodes both children's bytes with the same encoding, and mojibake
never equals `?`. Neutering the golib line and running under `chcp 437` fails it with
`stdout mismatch C# vs Go`; restoring the line passes in the same console.

One divergence remains, and it is stub-only: `Console.WriteLine` terminates with `Environment.NewLine`
(CRLF on Windows) where Go always writes `\n`, so baseline-stub output is mixed CRLF/LF — a `\n` inside a
`Printf` format string stays LF. The behavioral comparison reads both children line-by-line and so
normalizes this away; the full conversion does not have it at all, since `WriteFile` passes Go's `\n`
through unchanged.

## Source Generators
Several Go semantics cannot be written directly in C#, so the converter emits compact, attributed partial declarations and lets a set of Roslyn source generators (`src/gen/go2cs-gen/`, referenced as an analyzer by every converted project) synthesize the rest at compile time. This keeps the visible converted code close to the Go original. The principal generators and attributes:

* **`TypeGenerator`** — driven by `[GoType]`. Emits the body of a converted type: a struct's members and equality, a named numeric/slice/array/map/channel type's wrapper and operators (see [Named Numeric Types and Constant Contexts](#named-numeric-types-and-constant-contexts) and [Slices and Arrays](#slices-and-arrays)), and struct-embedding field/method promotion.
* **`ImplementGenerator`** — wires up Go's duck-typed [interfaces](#interfaces): finds the concrete types that satisfy each `[GoType] partial interface` and emits the implementation glue and implicit conversions.
* **`RecvGenerator`** — emits pointer-receiver overloads for receiver methods (`[GoRecv]`), so a method written against a value (`this ref T`) is also callable through the pointer/box form. A **variadic** method keeps its `params` in the generated overload: cryptobyte's `func (b *Builder) add(bytes ...byte)` emits the value form `add(this ref Builder b, params Span<byte> bytesʗp)`, but the `ж<Builder>` overload had dropped `params` (a bare `Span<byte>`), so a call passing individual elements through a box (`c.add(0xff)`, `c` a `ж<Builder>` closure parameter) could not bind it and fell back to the ref-receiver value method — CS1929. `GetMethodInfo` now preserves the `params` modifier (the Go variadic is always the last, non-receiver parameter, so it never lands on the `this ж<T>` receiver). Guarded by `VariadicBoxReceiver` (a `*sink` with `add(bytes ...byte)` called on a box — via a closure and directly — with zero, one, several, and spread arguments, values vs Go).
* **`ImplicitConvGenerator`** — emits the implicit conversion operators that let a [named type](#type-definitions) and its underlying types be used interchangeably.
* **`PartialStubGenerator`** — emits a throwing `partial` implementation for any bodyless `partial` method that has no other implementing part (e.g. assembly/cgo functions with no convertible body), while leaving real hand-written companion implementations untouched.

Common attributes the converter emits for the generators (and tooling) to consume: `[GoType]` (type bodies), `[GoRecv]` (receiver methods), `[GoTag]` (struct field tags), `[GoPackage]` (package info), and the test-only `[GoTestMatchingConsoleOutput]`. The full vocabulary — every stamp, where it lands, who reads it, and which of them are kept off the visible declaration — is classified in [Extended attributes: what stays on the declaration and what moves](#extended-attributes-what-stays-on-the-declaration-and-what-moves).

**A generator's view of ACCESSIBILITY is provisional — its own output is what supplies the access modifier (2026-07-25).** The converter emits a Go type as a bare `[GoType] partial interface X` (or `partial struct X`) nested in the package class and leaves the access modifier to `TypeGenerator`, which derives it from the Go export convention (`GetScope` — `public` for an exported name, `internal` otherwise; an explicit modifier on the converter's part wins). A C# nested type with no modifier is **private**, so until that generated partial exists the declaration is private — accessible from inside its own package class and *inaccessible from any other class in the assembly*. A generator cannot see its own output, so a semantic query that crosses package classes sees the provisional accessibility, not the real one.

That bit io's external test package, and it is the first shape in the corpus that can hit it: `io_test` and `io` compile into ONE assembly (the recompile test-project model), as two classes. `io_test.closer : io.Closer` and `io_test.testMultiWriter_sink : io.Writer` bound their base to an `IErrorTypeSymbol` (`CandidateReason.Inaccessible`, candidate `go.io_package.Closer/Interface/Private`) with **zero members**, so every method the base contributes silently vanished from the generated interface shell and adapters — while the FINAL compilation, which does have the generated `public partial interface Closer`, still demanded them: four distinct `CS0535` (six sites) across `Δcloser<T>`, `ΔcloserᴛObj`, `PipeReaderжcloser`, `PipeWriterжcloser`, `ΔtestMultiWriter_sink<T>` and `bytes_BufferжtestMultiWriter_sink`. The same `io.Closer` resolved normally as a base of `io.ReadCloser` *inside* `io_package`, and `fmt.Stringer` resolved normally from a referenced assembly (already `public` in metadata) — which is what made the failure look spelling-related. It is not: writing the base `global::go.io_package.Closer` fails identically.

`Common.GetAllBaseInterfaces` replaces the raw `AllInterfaces` walk in `InterfaceDeclarationSyntaxExtensions.GetInterfaceMethods` and in both of `ImplementGenerator`'s method-collection paths. It recovers an `Inaccessible` base from the error symbol's `CandidateSymbols` when the candidate is an interface declared in **this compilation's assembly**, and folds in that recovered base's own transitive bases (`AllInterfaces` cannot traverse through an error symbol). The recovery is sound rather than a bypass: the generator is about to declare that very type `public` or `internal`, both reachable from anywhere in the assembly. A genuinely inaccessible *foreign* type keeps its error symbol.

The deeper alternative — having the converter emit the access modifier on its own partial, so the pre-generation source stops understating it — would remove the whole class of provisional-accessibility blind spots, but writing that modifier onto the *inline* `[GoType]` declaration re-baselines every converted `.cs` in the corpus and in ~490 behavioral goldens, and coarsens the Go-shaped declaration the converter works to keep readable. It was left on the table here and taken the following day in the form below. (No behavioral guard is possible for this row: one behavioral project is one Go package is one C# assembly, so two package classes never share an assembly there. The reproducer is the `-tests` pipeline on `io`, whose six `CS0535` clear; gated by the full behavioral suite 490/490, the 302-package corpus build, and the bytes/strings/encoding/binary/strconv pipeline canaries all at banked counts.)

### `package_info.cs`'s `TypeAccessibility` section pins each type's accessibility IN SOURCE

**Resolved (2026-07-25).** The blind spot above is closed at the root, without touching a single converted `.cs`: `package_info.cs` gains a **`TypeAccessibility`** section, emitted **inside the package class body** (its entries are type declarations, and the types they name are nested in that class), carrying one condensed single-line partial declaration per converter-emitted `[GoType]` type:

```csharp
[GoPackage("io")]
public static partial class io_package
{
    // <TypeAccessibility>
    internal partial struct discard {}
    internal partial struct nopCloser {}
    public partial interface Closer {}
    public partial interface Reader {}
    public partial struct LimitedReader {}
    // </TypeAccessibility>
}
```

C# lets a partial type carry its access modifier on any **one** of its parts, so the inline `[GoType] partial interface Closer` stays bare and Go-shaped while this part fixes the accessibility — and it is fixed in **source**, so a generator's semantic query across package classes sees `public`/`internal` instead of the provisional `private`. The section carries its own explanatory prose in the style of the file's other sections; a package info file written before the section existed has the prose and markers inserted on the next conversion (`ensureTypeAccessibilitySection`), so no migration step is needed.

Details that make it a pure relocation of the modifier rather than a change of it:

* **The rule is `TypeGenerator`'s, mirrored exactly.** `recordTypeAccessibility` uses the explicit modifier the converter emitted inline (the publicization pre-pass's `public`) when there is one, and otherwise `generatedTypeScope`, a Go-side mirror of `Common.GetScope`. Deliberately **not** `getAccess`: `GetScope` reads the C# identifier verbatim, so a `Δ` collision-rename (a Greek capital) reads as exported where `getAccess` strips the prefix first. Mirroring the generator is what keeps the corpus's effective accessibility byte-for-byte unchanged and avoids `CS0262` between the two parts.
* **Generic types repeat the type-parameter list, never the constraints** (`internal partial struct entry<K, V> {}`) — a partial declaration may leave constraints to another part. An arity-0 *constraint* interface repeats the CRTP `<ΔT>` marker list the inline declaration carries.
* **Every partial-emitting kind is covered:** struct (defined struct, map, channel, slice/array, defined-over-selector), class (named pointer types), interface. A named **func** type is emitted as a C# `delegate`, which is not partial and already carries its own modifier, so it has no entry.
* **Hand-owned files are deliberately excluded.** A file whose destination `.cs` carries `[module: go.GoManualConversion]` has its converter output redirected to the non-compiled `.cs.auto` sibling, so the declarations that actually compile are the author's — their kind, name and modifier are the author's to choose, and a generated entry could contradict them (`CS0261`/`CS0262`) or conjure a phantom empty type. Across the 302-package corpus that leaves 19 marked files, of which 12 declare `[GoType]` types (~48 declarations, mostly `runtime/runtime2.cs`) — and exactly **one** interface among them, `sync.Locker` in the hand-rewritten `sync/mutex.cs`. That single declaration is why **`Common.GetAllBaseInterfaces`'s `Inaccessible` recovery is RETAINED** rather than retired: it is now a narrow backstop for hand-owned declarations instead of the primary mechanism. Writing the modifier explicitly on a hand-owned declaration removes the need for it there.
* **Test variants get the same treatment, split by owning class.** Under the recompile test model the production entries reach `package_test_info.cs` through the verbatim seed from `package_info.cs`, the internal variant's test-local types merge into that same (production-class) section, and the **external** variant's types — which live in `<pkg>_test_package` — go to `package_info_external_test.cs`, whose first and only class is the test class. `writeExternalVariantMetadata` clears the accessibility set before writing `package_test_info.cs` so a test-class type can never land in the production class, and the unit is now written whenever the external variant declares types, not only when it records test-anchored `GoImplement`/`GoImplicitConv` attributes. Under ordinary reference the test class is the anchor; under white-box reference the external test class is preferred, with the internal bridge used when no external variant exists.
* **The generator still writes the modifier on its own part** — the section is a third part with the *same* modifier, which C# accepts. Removing it is not safe while the section deliberately does not cover hand-owned declarations: those parts would become `private`.
* **Generator-side consequence:** a converted type now has two converter-written parts, so any lookup that resolves a type by **name** through the syntax trees can land on either. `Compilation.FindStructDeclaration` and `ImplicitConvGenerator.GetStructDeclaration` — both of which go on to read members or the `[GoType]` definition token — now prefer the part carrying `[GoType]` (`Common.IsGoTypeDefinition`), falling back to first-match. Without that, syntax-tree order (i.e. compile-item order, where `package_info.cs` sorts ahead of the package sources in roughly half the corpus) would silently decide.

Measured, with a positive control (the `-tests` pipeline on `io`, whose `CS0535` cluster is the reproducer): recovery **on** + section on → 0 `CS0535`; recovery **neutered** + section on → 0; recovery neutered + section **off** → the cluster returns. Gates: full behavioral suite 490/490 across all four phases (460 output-compared, 30 skipped) with **every** main `.cs` golden byte-identical — the churn is 490/490 `package_info.cs`, additions only; seeded 305-package reconvert (14/14 `.cs.auto`, no marked file clobbered) + overlay + full corpus build 0 errors; converter `go test` and `GenTests` green; pipeline canaries at banked counts (errors 61, encoding/csv 71, io/fs 18, bytes 81 with 7 disclosed).

### Extended attributes: what stays on the declaration and what moves

The `[GoType]` declaration is the line a reader of converted code actually reads, so every *other* attribute stamped on it is machinery competing with the Go original for that reader's attention. `package_info.cs` already exists to hold per-type records out of view, and the `TypeAccessibility` section above already moved the access modifier there. **A stamp can follow it whenever its consumer reads the attribute off the TYPE rather than off a particular declaration** — C# unions the attributes of every part of a partial type, so which part carries one is invisible to runtime reflection and to any generator that resolves the symbol.

That single criterion classifies the whole surface. The converter stamps nothing from the BCL — every `[StructLayout]`, `[MethodImpl]` or `[LibraryImport]` in the corpus is in `golib` or in a hand-owned file — so the vocabulary is exactly this:

| Stamp | Lands on | Consumer | Verdict |
|---|---|---|---|
| `[GoType]`, `[GoType("dyn")]`, `[GoType("num:…")]`, … | struct / class / interface | `TypeGenerator`'s syntax receiver keys on it; also read semantically and at runtime | **Must stay** — it is the declaration's identity, and the receiver has no type to resolve until it matches |
| `[GoValueClone("f1", "f2")]` | struct | `TypeGenerator`, reading field names to emit `Clone()` | **Moved** |
| `[GoLocalName("Point")]` | struct (lifted function-local named type) | golib's reflection bridge, `GoReflect.TypeNaming` | **Moved** |
| `[GoTag("json:\"x\"")]` | **field** | golib reflection, via the `DescriptionAttribute` alias | **Must stay** — field-level. A `<TypeAccessibility>` record is an empty `{}` body; C# has no way for a second part to re-declare a field and attach an attribute to it |
| `[GoRecv]` | **method** | `RecvGenerator` syntactically, plus runtime | **Must stay** — same reason, one level up: a method exists on the part that defines its body |
| `[GoArrayDims(4, 8)]` | **parameter** | golib reflection — `GoReflect.FuncParamDims` off the delegate instance's `Method.GetParameters()`, or `MethodParamDims` off the method table's `ParameterInfo`s | **Must stay** — the sharpest case in the set: the datum is not type-keyed at all. It distinguishes two funcs that share one emitted delegate type (`func([32]byte) bool` and `func([64]byte) bool` are both `Func<array<byte>, bool>`), so a record keyed by type has nothing to key on, and the consumer reads the parameter's own metadata |
| `[GoInit]` | **method** | the **C# compiler** — it is a `using` alias for `ModuleInitializerAttribute` | **Must stay** — the compiler requires it on the method it initializes with |
| `[GoPackage]`, `[GoImplement<T,I>]`, `[GoImplicitConv<S,T>]`, `[GoTypeAlias]` | package class / assembly | generators, runtime, and the converter's own next run | **Already there** — these are emitted into `package_info.cs` and never touched a mainline declaration |
| `[GoManualConversion]`, `[GoRequiresUnsafe]` | module | the converter | **Already off** — hand-written, module-scoped |
| `[GoInterfaceShell]`, `[GoReflectCompanion]` | interface / field | golib | **Not converter-emitted** — written by the generator and by hand respectively |

So the movable set is `[GoValueClone]` and `[GoLocalName]`, and both moved. `[GoType] [GoValueClone("intbuf")] partial struct pp {` reads `[GoType] partial struct pp {`, with the record in `package_info.cs` carrying the rest:

```csharp
    // <TypeAccessibility>
    [GoValueClone("grid")] internal partial struct holder {}
    [GoValueClone("b")] internal partial struct inner {}
    internal partial struct row {}
    // </TypeAccessibility>
```

Mechanics worth knowing:

* **The attributes and the access modifier travel together, by construction.** `recordTypeAccessibility` takes the stamps as an argument and returns what the caller must still write inline — empty when the record absorbed them. So the two paths that write **no** record keep the stamps on the declaration and cannot lose them: a hand-owned file (whose emission goes to the non-compiled `.cs.auto` review sibling) and a `-tests` bridge unit (whose accessibility is inline because its metadata anchor can be a different test class). A hand-written conversion therefore still stamps `[GoValueClone]` inline, and it still works.
* **`TypeGenerator` reads the stamp across every partial declaration**, starting with the `[GoType]` one its receiver matched and continuing through the symbol's other `DeclaringSyntaxReferences`. That is what makes both placements equivalent rather than one replacing the other. The match itself stays syntactic, as it is for every attribute this generator reads.
* **The section sorts on the DECLARATION, not the line.** `typeAccessibilityKey` strips the attribute prefix before comparing, so a stamped entry keeps the place its accessibility/kind/name earns instead of being pulled into a leading block by its `[`. Sorting the raw line is legal but scrambles a section whose whole value is being readable at a glance.
* **Not a semantic change anywhere.** The relocation moves *where the attribute is written*, exactly as the `TypeAccessibility` section moved where the modifier is written. Nothing observes a difference: the generated `Clone()` is identical, and `GoReflect`'s `%T` output is identical.

## The standard-library conversion applies `-tags purego`

The converted standard library reproduces **Go built with `-tags purego`**, not the default `amd64`/`arm64` build. This is a fidelity decision, not a convenience one: Go implements hot cryptographic and hashing functions in hand-written `.s` assembly, with the Go source carrying only a bodyless declaration (e.g. `crypto/sha256/sha256block_decl.go` is `//go:build (386 || amd64 || s390x || ppc64le || ppc64) && !purego` and declares `func block(dig *digest, p []byte)` with no body). A transpiler works from Go source and cannot convert assembly, so those declarations become **throwing stubs** — they *compile* (they are part of the 302-package clean-compile milestone) but cannot *run*, which blocks Phase-4 validation. A managed C# runtime can never execute those `.s` files, so "Go built with `-tags purego`" is a claim go2cs can actually honor, whereas "the default amd64 build" is one it fails on every asm-backed function. The `purego` build tag selects the portable pure-Go variants instead (`sha256block_generic.go` is `//go:build purego || !(386 || …)` and has a real body), replacing ~42 stubs across ~13 packages with convertible code. Adopting the tag drops **zero** compiling packages (302/302 with the tag, 302/302 without) and unblocks running.

**How the tag is applied — and made visible, not magic.** `-stdlib` applies `-tags purego` **by default**; an explicit `-tags` on the same command overrides it verbatim (including `-tags=` to clear it and reproduce the asm-backed default build). **`-tests` gets the SAME default** (`resolveBuildTags`): a `-tests` run reconverts the package's PRODUCTION sources and recompiles them into the test assembly, so it must select the exact same source files the committed corpus was built from — that tree *is* Go-under-purego. Without it, a package whose asm and pure-Go variants are gated `!purego`/`purego` (crypto/subtle's `xor_amd64.go` and `xor_generic.go`, both declaring `func xorBytes`) has BOTH files converted and collides (CS0111 duplicate member), and the regenerated production `.cs` diverges from the committed purego emission. The default is scoped to `-stdlib` and `-tests` — the whole-library corpus and its test validation — because the purego claim is about *what the corpus is*, so it must hold for every such invocation (direct or via any script), not depend on remembering a flag. Scripts-only (having the deploy/convert scripts pass the tag) was rejected for that reason: the canonical `go2cs -stdlib` invocation is used throughout the repo and docs, and a script-gated tag would make a bare `-stdlib` produce a *different* corpus. Crucially the default does **not** touch `-recurse` end-user conversions or single-file/dir conversions — there the user's own build tags govern, and a hidden default would be least welcome. Discoverability is threefold: the `-stdlib` and `-tags` `--help` text both state the default, and the stdlib converter prints the effective tags at the start of every run (`Applying build tags: purego (default; pass -tags to override)`, or `… none (-tags= cleared the purego default)` when overridden). The tag threads into both the `go/packages` loader **and** the converter's own `BuildConstraintEvaluator` (seeding only the loader would silently re-exclude the file the tag just selected).

**`purego` is not the only spelling of this decision — `math_big_pure_go` is the same one.** The tag set the corpus applies is `purego, math_big_pure_go`, because `purego` is a *convention* the crypto and hashing packages adopted, not a language rule, and `math/big` predates it. `math/big` gates its own portable fallbacks on `math_big_pure_go`: `arith_decl.go` is `//go:build !math_big_pure_go` and declares **eight** bodyless `//go:linkname`/`//go:noescape` functions — `addVV`, `subVV`, `addVW`, `subVW`, `shlVU`, `shrVU`, `mulAddVWW`, `addMulVVW` — whose bodies are `arith_$GOARCH.s`, while `arith_decl_pure.go` is `//go:build math_big_pure_go` and forwards each to the `_g` pure-Go implementation that already sits in `arith.go`. With only `purego` seeded the converter selected the declaration file, so all eight became throwing partial stubs: `math/big` **compiled clean and could not run** — every `big.Int`, `big.Float` and `big.Rat` arithmetic path raised on first use. (It surfaced as a single `time` verdict, `TestTruncateRound` → `big.Int.Mul` → `mulAddVWW`, a good illustration of how little a visible symptom says about the size of its root.) Selecting `arith_decl_pure.go` also drops `arith_amd64.go`, whose only declaration is `support_adx` — read exclusively by the assembly that is not there either. The general rule this states: **a package's portable-fallback tag belongs in the default set whatever it is called**; the taxonomy below classifies by whether a portable sibling *exists*, not by whether it happens to be spelled `purego`. `defaultStdLibBuildTags` is content-pinned by `TestDefaultStdLibBuildTagsContent`, which fails on an undocumented addition as well as a silent removal.

**The asm-stub taxonomy.** A Go declaration that a platform build binds to assembly falls into one of three buckets, each with a distinct treatment:

* **`purego`-gated** — the file is `… && !purego` and a portable `… || purego` sibling exists (crypto/hashing hot paths: `crypto/sha256`, `crypto/sha512`, `crypto/md5`, `crypto/sha1`, `crypto/aes`, `vendor/…/poly1305`, `vendor/…/chacha20poly1305`, `vendor/…/sha3`, `maphash`, …). **The tag selects the real body** — no hand-owning needed. This is the large majority and the whole point of adopting purego.
* **GOARCH-gated with no `purego` escape** — the platform file is gated only on architecture (`amd64 || arm64`) with no `|| purego` alternative, so the tag cannot reach a pure-Go variant (`internal/chacha8rand`'s `chacha8_amd64.s`; `hash/crc32`'s `crc32_amd64.go`, which carries no `//go:build` line at all — its only constraint is the `_amd64` filename suffix, so `go list -tags purego hash/crc32` selects exactly the same files as the default build). These are hand-owned: either as a `<name>_impl.cs` companion or as a whole-file rewrite (the mechanisms in *Manually-Converted Declarations* below). Hand-owning does **not** have to mean stubbing — where .NET exposes the same instructions the `.s` file issues, the arch layer can be realized for real; see *[Realizing an asm-backed arch layer with managed hardware intrinsics](#realizing-an-asm-backed-arch-layer-with-managed-hardware-intrinsics)*.
* **Genuinely raw-metal** — memory-layout math, type-descriptor walking, or `*.asm` with no portable Go form at all (`internal/abi`, `internal/bytealg`, parts of `runtime`). These are stubbed with `[module: GoManualConversion]` — a stub that compiles is an acceptable milestone solution and the operational body lands with the future `getg()`/thread-context model.

### Known accepted divergence — `crypto/elliptic` P256 `Inverse` panics under purego

One package regresses *behaviorally* under purego relative to the default build, and it is recorded as a **known divergence, not fixed**, because matching it is fidelity: **real Go panics there too under `-tags purego`.** Upstream `crypto/elliptic/nistec_p256.go` is gated `//go:build amd64 || arm64` **without** `&& !purego`, while its dependency `crypto/internal/nistec/p256_ordinv.go` is `(amd64 || arm64) && !purego`. So under purego the ordinv fallback `p256_ordinv_noasm.go` — which returns `errors.New("unimplemented")` — is selected, and `crypto/elliptic`'s `Inverse` (reached via the deprecated `invertible` interface) treats **any** error from the nistec scalar inverse as an invariant violation and **panics** `crypto/elliptic: nistec rejected normalized scalar`. (`crypto/ecdsa` handles the same error correctly with a Fermat-little-theorem fallback, so ECDSA is unaffected.) Verified against real Go: the default build returns a valid inverse, and `go run -tags purego` panics with that exact message. This is an upstream Go inconsistency in a largely-superseded package; go2cs reproduces Go-under-purego faithfully, so the panic is expected. It is a divergence from the **default** (`-tags=`) build only, which binds the asm ordinv.

## Manually-Converted Declarations

Some Go declarations cannot be faithfully auto-converted because their semantics depend on hiding a managed pointer inside an integer. The canonical family is runtime's `guintptr`/`puintptr`/`muintptr` (`type guintptr uintptr` holding a `*g` the Go GC must not see): the CLR has the *opposite* constraint — a managed reference stored as a number is invisible to the .NET GC, so the referent can be collected or moved and the number is garbage. The managed conversion stores the `ж<T>` box **directly** and the numeric form never exists (model precedent: `core/sync/atomic`'s hand-rewritten `Pointer<T>`).

Two mechanisms deliver this, chosen by granularity:

* **Whole-file** (pre-existing): a hand-finished file marked `[module: GoManualConversion]` is never overwritten by the converter when it exists in place (`containsManualConversionMarker`), and is restored over auto output by the overlay on fresh (unseeded) reconversions. Right when the whole file is hand-owned (sync/atomic `type.cs`). The marked file's Go source is NOT dropped from conversion (2026-07-17 fix): it is still analyzed and visited with its package — its anonymous-struct lifts, package-var registrations, and other package-wide state must keep feeding the package's sibling files, or those emit corrupted (raw Go `struct{…}` text in selectors, package-var assignments re-declared as shadowing locals) — with emission redirected to a non-compiled `<name>.cs.auto` review sibling. Guarded by the `ManualConversionSiblingState` behavioral test.
* **Type-level** (`go2cs/manualTypeOperations.go`): the `manualConversionTypes`/`manualConversionFuncs` registry (keyed by package path and raw Go names) makes the converter skip emitting the listed **type declarations**, every **method on those types**, listed **adjacent free functions** (`setGNoWB`), and **`GoImplicitConv` assembly attributes** referencing the types — each replaced by a marker comment pointing at the package's `*_impl.cs`. Right when the types live in a large file (runtime2.go) that must otherwise keep receiving converter improvements. Each `manualConversionFuncs` entry also carries a **platform scope** — see *Hand-owns have a platform* below.

The hand implementation (`src/core/<pkg>/<file>_impl.cs`, e.g. `core/runtime/runtime2_impl.cs`) declares the same type/extension surface the auto call sites bind: value-receiver methods as `this T` extensions, pointer-receiver methods as `[GoRecv] this ref T`, and the conversion operators call sites need. For the guintptr family that surface is: `.ptr()` returns the stored box, `.set()` stores it, `.cas()` is a real `Interlocked.CompareExchange` on the reference slot (the Go original's `atomic.Casuintptr` maps to a throwing asm stub — the managed model makes it *work*), `== 0`/`= 0` bind zero-comparison/nil operators, and numeric escapes are deliberate and loud: converting a non-zero integer **panics** (a number can never faithfully become a managed reference), and converting *to* a number (print/`hex` diagnostics) yields a stable object-identity hash — an opaque token, never an address.

One call-site emission cooperates (`convCallExpr.go`): a conversion **to** a manual type from an `unsafe.Pointer` — `guintptr(unsafe.Pointer(newg))` — unwraps the inner conversion and emits the referent-preserving ctor form `new Δguintptr(newg)` instead of the numeric cast chain `(Δguintptr)(uintptr)new @unsafe.Pointer(newg)`, which would lose the referent at the `(uintptr)` hop.

**The runtime lock/note model (`core/runtime/lock_managed_impl.cs`).** Go's `mutex.key` is a tagged atomic slot — 0 unlocked, `locked` (1) held, or an `*m` address|locked heading a waiter chain through `m.nextwaitm`, parked on OS semaphores. The managed model hand-owns `mutexContended`/`lock2`/`unlock2`/`notewakeup`/`notesleep`/`notetsleep_internal` (via the same registry; thin wrappers and consts stay auto) and keeps the **same key protocol restricted to `{0, keyLocked}`**: the mutex is an `Interlocked` spinlock on the real `key` storage with `SpinWait` escalation standing in for the spin→yield→park ladder; the note is a signaled/clear latch (double-wakeup throw preserved; timeout at millisecond granularity). Deliberately not modeled, documented in place: the waiter queue (fairness), lock profiling, and the `m.locks`/preempt bookkeeping — `getg()` is a Go compiler intrinsic with no managed realization yet (a `[ThreadStatic]` g/m model is the future root that unlocks runtime-operational semantics; the bookkeeping returns to these bodies when it lands).

Go actually has **two** flavors of that protocol and picks one per GOOS: `lock_sema.go` (windows, darwin, plan9, aix …) parks on OS semaphores as described above, and `lock_futex.go` (linux, freebsd, dragonfly) uses a `{0,1,2}` slot and parks on a futex. Neither primitive survives conversion, so both collapse onto the identical managed answer — which is why the core above is **one flat, platform-neutral file** and not a copy per flavor. What genuinely differs is a single signature: `notetsleep_internal` is `(n, ns, gp, deadline)` in `lock_sema.go` and `(n, ns)` in `lock_futex.go`. Each flavor keeps only that declaration, four lines delegating to the shared `noteSleepDeadline`, in `runtime/{windows,darwin}/lock_sema_impl.cs` and `runtime/linux/lock_futex_impl.cs`. `keyLocked` is the managed spelling of the value Go calls `locked` on one flavor and `mutex_locked` on the other — both 1, and neither name is declared on the other flavor's platforms.

### The managed netpoller — the ten `runtime_poll*` contracts on .NET's completion machinery

**The fifth and deepest application of the managed-API-boundary pattern, and the one the sockaddr entry above predicted.** `internal/poll` declares **ten** bodyless `//go:linkname` entry points into the runtime's network poller (`fd_poll_runtime.cs:18–36`). The converter emits each as a bodyless `partial`, the `PartialStubGenerator` fills them with throwing stubs, and the first pollable `FD.Init` — which is *every socket the `net` package creates* — died in `serverInit.Do(runtime_pollServerInit)`. `os` is unaffected and always was: it passes `pollable: false` for every file, pipe and console, so `runtimeCtx == 0` short-circuits every pd call.

**Why the counterparts could not simply be wired.** They exist — `runtime/netpoll.cs:217` carries `poll_runtime_pollServerInit` with its linkname comment intact, and all nine others sit beside it — but the SHALLOW wall (`netpollinit` → `stdcall4(_CreateIoCompletionPort, …)` → `asmstdcall`, a stub) is not the real one. Behind it the bodies consume runtime mutexes with lock-rank bookkeeping, `pollcache` over `persistentalloc`, the runtime timer engine, `gopark` with a commit callback, `goready`, and g-pointer CAS protocols — every one an organ of the Go scheduler. The decisive fact is that Go's poller is only **half** an API: the other half (`netpoll(delta)`, `netpollBreak`, `netpollready`) is called by the scheduler itself from `findrunnable` and sysmon. Under go2cs nothing would ever pump it, so a perfectly-wired conversion would initialize an IOCP and then **block forever** — the thread Go dedicates to draining it IS the scheduler. The ten-contract boundary is the only cut through this subsystem that does not drag a scheduler across; `runtime/netpoll.cs` and `runtime/windows/netpoll_windows.cs` stay converted and **dead**, with zero runtime edits.

**The shape.** `internal/poll/windows/runtime_netpoll_impl.cs` (per-GOOS folder, riding the existing `$(GoTargetOS)/*.cs` glob — **no csproj change**) supplies the ten bodies over a `ManagedPollDesc`: one Monitor, a sticky `closing`, and per mode a `ready` flag, a sticky `expired` flag, a generation counter and a `System.Threading.Timer`. Four decisions are worth cribbing:

- **One Monitor, not Go's lock-free `rg`/`wg` CAS protocol.** Go splits `pollDesc` state across atomics because `netpollcheckerr` runs where the pd lock cannot be taken and `netpollblock` parks through `gopark`'s publication protocol. Neither constraint survives the boundary — every managed caller is an ordinary blocked thread — and the single-waiter-per-mode contract (*"pollDesc can hold only a single waiting goroutine for each mode"*, enforced above by `fdMutex`) bounds contention to one reader + one writer + one timer callback. The CAS choreography would be emulation of a mechanism whose reason evaporated, the same reduction `runtime_sema_impl.cs` made for the runtime semaphore.
- **Completion delivery is `ThreadPoolBoundHandle` — the CLR's own IOCP**, so there is no poller thread of ours, no shutdown story, and no raw-address routing table. `isPollServerDescriptor` therefore answers **false** for every fd: there is no exposed poll-server descriptor, and its only consumer is the test-only `IsPollDescriptor`. The association is made **lazily, at the first submit** — not at `pollOpen` — and that placement is load-bearing rather than incidental. Go's poller *rejects* completions it does not own (`pollOperationFromOverlappedEntry` compares the completion key against the `pollDesc` recorded in the operation and drops a mismatch, go.dev/issue/58870); `ThreadPoolBoundHandle` has no equivalent, because its callback resolves state *from* the `NativeOverlapped` it allocated, so a foreign overlapped is **misread rather than ignored**. Binding at open would make every pollable socket eligible for that, and `internal/poll` has a live producer: `FD.WSAIoctl` bypasses `execIO` and hands the kernel the *caller's* `syscall.Overlapped` — which, being an all-scalar struct in a standard box, genuinely pins and genuinely reaches the kernel. Binding at first submit removes the hazard structurally: a socket that only ever sees foreign overlapped IO is never associated at all. (Both production `FD.WSAIoctl` callers pass a `nil` overlapped, so no corpus path mixes the two on one socket.)
- **The load-bearing separation is wake-vs-readiness.** A completion sets `ready` and pulses; a deadline firing and `pollUnblock` pulse **without** setting it. Go encodes this as `pdReady`-vs-`pdNil`-wake, and it is what makes `waitCanceled`'s ignore-errors loop correct — that loop waits for a COMPLETION specifically, ignoring the very timeout that woke its caller. Readiness is also consumed *before* either error check, so a completion that raced the deadline is still delivered.
- **Deadlines are `Timer` + sticky flags + generations, not `CancellationToken`.** A CTS models a one-shot cancellation of a linked operation; a Go deadline is per-MODE, STICKY across future operations until re-set, REPLACEABLE while an op is in flight, and NON-ABANDONING (the timed-out op must still be cancelled *and harvested* by the same caller). Modeling all four with tokens reconstructs the flags+timer state machine anyway. The generation check is **not** optional hardening: `Timer.Change`/`Dispose` do not synchronize with an in-flight callback, so without it a cleared deadline can be expired by the callback of the deadline it replaced.

**Landed in stages, and only the first is banked here.** S1 covers the listener lifecycle — contracts 1 (`pollServerInit`), 2 (`pollOpen`), 8 (`pollUnblock`), 3 (`pollClose`), plus a `pollSetDeadline` smoke — with **zero data flow**. The overlapped submit seam is a genuinely separate wall and is S2's: `execIO` hands `&o.o` to `WSARecv`/`WSASend`/`AcceptEx`/`ConnectEx`, and that address is an interior field inside a reference-bearing container, which `ж.cs` states plainly it cannot hold still ("is left exactly as it was — a transient address"). Handing the kernel a transient interior address for an operation it retains for seconds is the pipe-EOF defect with an unbounded window, and the OVERLAPPED doubles as the operation's kernel-side IDENTITY (`CancelIoEx` matches by address), so a fresh native copy per call would break cancellation outright. (Guarded by the `NetListenSmoke` behavioral **output** test, which prints kernel-derived values rather than checking for absence of a fault — an ephemeral port was assigned, two live listeners differ, and, the strongest line, the port a closed listener released can be re-bound, which is only true if `pollClose` released the registration before `internal/poll` closed the socket. Port numbers themselves are never printed, so the output is host-independent.) Full design, and the eight ruled open questions: [`docs/phase4/DESIGN-netpoll-managed-poller.md`](phase4/DESIGN-netpoll-managed-poller.md).

### A hand-owned file can declare that it needs `/unsafe`

`<AllowUnsafeBlocks>` is converter-generated from `usesUnsafeCode`, and `usesUnsafeCode` is an **emission** fact: it is set while visiting Go source, so it sees only C# the converter itself wrote. A hand-owned file is by definition code the converter did not write. That left a hole with no honest way through it — a package whose only need for `/unsafe` was hand-written could not express it, because the `.csproj` is regenerated on every transpile and any value set by hand is undone by the next reconvert overlay.

`[module: go.GoRequiresUnsafe]` closes it. The declaration lives in the file that HAS the requirement, and the emission unions it into the property:

```csharp
// core/time/time_impl.cs — `time`'s converted emission contains nothing unsafe at all
[module: go.GoRequiresUnsafe]

namespace go;
```

It is a **union**, and inert for the same reason the cross-platform union in `platformEmit.go` is: the property *grants* a capability rather than using one, so raising it moves no IL for code containing nothing unsafe. Together the two unions mean a `.csproj` says `true` when the converter's own emission needs it on **any** target, or when **any** hand-owned file in the package declares it.

Shape follows the [`GoManualConversion`](#manually-converted-declarations) precedent exactly — a module-scoped attribute class in `golib`, detected by scanning the file's header text — and shares that marker's **scan** rather than adding a second one, so it inherits the same comment lexer: a `/*` that is ordinary prose inside a `//` line comment opens no block, and a marker *mentioned* in a comment is not a declaration. Both marker names live in the canonical symbol table (`src/core/go2cs/symbols.json`), because each is one string spanning a C# attribute declaration and a Go regexp and belongs in neither.

The walk is the package's own files plus its per-GOOS source folders, **never recursive**: a converted package directory can hold nested packages (`internal/runtime` holds `syscall`, `atomic`, …) whose own `.csproj` answers for them, and the discriminator is layout L3's own — a per-GOOS folder holds no project file — so `internal/syscall/windows` is not read as its parent's Windows sources. The declaration is per **package** rather than per platform because a `.csproj` is one file serving every `$(GoTargetOS)`.

Like the hand-own marker itself, this reads the OUTPUT tree, so it inherits the same prerequisite: a reconvert must be **seeded** from the committed corpus, or there is nothing on disk to declare anything. That is already the standing reconvert ritual, so it adds no new rule.

Three packages declare it today, all of them at a kernel boundary: `internal/runtime/syscall` (the Linux `Syscall6` keystone) and `time` flip from `false`, and `syscall` states a requirement its converted emission already happened to satisfy — inheriting a requirement by luck is how it disappears. See [Every P/Invoke is source-generated](#every-pinvoke-is-source-generated) for what the flag is spent on.

### Hand-owns have a platform, and it is not the same question as a folder

Two different mechanisms answer two different questions, and conflating them is what produced the Linux corpus's whole class-(b) failure surface (`docs/phase4/DESIGN-multiplatform-corpus.md` §12, increments 3.5 and 3.5b).

* **Where a hand-owned FILE is built** is layout L3's question, answered by the file's *principal*: an `*_impl.cs` takes part in exactly the platform builds the `<name>.cs` it supplements does; a marked whole-file hand-own follows its `.cs.auto` review sibling. Every platform ⟹ flat; a subset ⟹ one copy per platform in the subset. Routed automatically by the reconvert merge and guarded by a walk of the real corpus (`platformHandOwn_test.go`).
* **Whether a DECLARATION is hand-owned at all** is the registry's question, and until 2026-08-08 it had no platform axis: `manualConversionFuncs` was keyed by name alone. A Go name is not unique across platforms — Go selects one of several files declaring the same function by build constraint — so one entry spoke for every flavor, turning each one's declaration into a placeholder while an implementation existed only where somebody had written one.

Entries now carry a `goosScope`, whose empty value (`goosAny`) means every target and is what nearly all ~120 entries use. Scoping is load-bearing in both directions:

| Entry | Scope | Why |
|:--|:--|:--|
| runtime's `mutexContended`, `lock2`, `unlock2`, `notewakeup`, `notesleep`, `notetsleep_internal` | `goosAny` | Both flavors need hand-owning; the arity difference is the *file's* problem, not the registry's |
| `os.(*File).readdir` | windows, darwin | Those flavors hand OS memory to a Go struct (`FILE_ID_BOTH_DIR_INFO` reinterpreted; libc `readdir_r(&dirent)`). `dir_unix.go`'s is **pure Go** over `internal/poll` and converts faithfully |
| `os.readReparseLink`, `syscall`'s five generated wrappers | windows | Declared only in Go's Windows sources; already inert elsewhere, now stated rather than re-derived |

The `os.(*File).readdir` row is the one that cost something: unscoped, the entry deleted the perfectly convertible unix body too, so every Linux `os` build carried a placeholder with nothing to link against — a hand-own gap **invented by the registry rather than by the Go source**. Scoping it out is why Linux needs no `readdir` hand-own at all, which is strictly better than writing one.

What a scope deliberately does **not** express is a per-platform *signature*. The registry decides whether a declaration is hand-owned; the hand-owned file decides what it looks like. Guarded by `manualConversionScope_test.go`, whose fixture is the `lock_sema`/`lock_futex` pair at their real 4-vs-2 arity, plus a typo guard — a scope naming an unknown GOOS matches nothing, which would silently turn a hand-own off everywhere and is otherwise unreportable, since "not hand-owned" is a legitimate answer for every other declaration.


**`crypto/subtle`'s word-at-a-time XOR (`core/crypto/subtle/xor_generic.cs`, whole-file).** `xorBytes` XORs a machine WORD at a time by reinterpreting its three byte slices as `[]uintptr` (`unsafe.Slice((*uintptr)(unsafe.Pointer(&x[0])), len(x)/wordSize)`). A `uintptr[]` view over a `byte[]` does not exist in the managed model — golib's `slice<T>` is a window on a real `T[]` — so the converted `words()` could only SNAPSHOT the bytes into a detached `slice<uintptr>`, and the word loop XORed the snapshot and dropped it: for every length that is a multiple of 8, `XORBytes` wrote **nothing**. The whole file is hand-owned (marked `[module: GoManualConversion]`) and does the same reinterpret the managed way, `MemoryMarshal.Cast<byte, ulong>` over the slices' own spans — a genuine aliasing view, so the word writes land in place — keeping Go's word-at-a-time behavior and the performance contract crypto/cipher's CTR and GCM modes depend on. Only Go's `supportsUnaligned`/`aligned` gate is dropped (it exists for architectures whose unaligned word loads fault). Full detail: *`unsafe.Slice` over MANAGED element storage ALIASES it*. Guarded by crypto/subtle's own suite (7/7, no disclosures, over the full 1..1024 x 8 x 8 x 8 alignment matrix).

**`sync/atomic.Value` (`core/sync/atomic/value.cs`, whole-file).** Go's `atomic.Value` stores and loads an `any` atomically by reinterpreting the interface's internal two-word `(type, data)` layout: `(*efaceWords)(unsafe.Pointer(&v))`, then `atomic.LoadPointer`/`StorePointer`/`CompareAndSwapPointer` on the `typ` and `data` slots, with a `firstStoreInProgress` sentinel guarding the first store. That layout is a Go runtime detail with **no managed equivalent** — an `any` here is a single `System.Object` reference (one word), and reinterpreting a managed reference as a raw address to poke type/data words simply NREs (the same managed-referent-through-`unsafe.Pointer` wall as the guintptr family). The first *operational* hit was `internal/testlog`'s package-level `var logger atomic.Value`, loaded during `os.Getenv` — so `atomic.Value.Load()` NRE'd on the zero value before any store. The whole file is hand-rewritten (marked `[module: GoManualConversion]`) to store the `any` **directly** in the `Value.v` field and use `Volatile.Read`/`Interlocked.CompareExchange` for the acquire/release ordering and CAS the literal conversion cannot provide; the nil-store and inconsistent-type panics, and `CompareAndSwap`'s by-value comparison (`AreEqual`, matching Go's `i != old`), preserve the spec. Guarded by the `AtomicValue` behavioral test (Load-nil / Store / Swap / CompareAndSwap over typed string values, output-compared vs Go).

**The `internal/reflectlite` mini-bridge (`value_impl.cs` + `swapper_impl.cs`, Phase-4 reflection
bridge).** `sort.Slice`/`SliceStable`/`SliceIsSorted` route through reflectlite —
`ValueOf(x).Len()` and `Swapper(x)` — and the auto forms reinterpret the interface's eface
`{type,data}` words, so the first touch dereferenced a nil `ж<abi.Type>` (sort's `TestSlice`, the
first operational hit: `unpackEface` → `abi.Kind` → NRE). The fix mirrors the full `reflect`
bridge (see `reflect/value_impl.cs` and `docs/phase4/DESIGN-reflection-bridge.md`) for exactly the
mini-surface sort exercises: `ValueOf`/`unpackEface` build the `Value` over a companion
`partial struct Value { object boxed }` field — `typ_` takes the Phase-1 synthetic `abi.Type` and
the flag takes the Kind bits, so `Kind()`/`IsValid()` keep working from the auto `value.cs`
unchanged — `Value.Len` reads the boxed value through the golib container interfaces
(`@string`/`IArray`/`IMap`), and `Swapper` swaps through golib's non-generic `ISlice` indexer
(which applies the slice window offset, so swaps land on the shared backing store exactly like
Go's). The four declarations are skipped by the converter via the `manualConversionFuncs`
registry (`"internal/reflectlite"` in `go2cs/manualTypeOperations.go`); the rest of reflectlite —
including `packEface`/`Interface()` (used by `errors.As`) — stays auto and is NOT yet operational.
Verified by the sort differential: `TestSlice` flips to pass — `sort.Slice` sorts through the
managed Swapper, and its closing `SliceIsSorted` check reads length through the same `ValueOf` path. Go's version counts **mallocs**: it pins `GOMAXPROCS(1)`, runs `f` once as a warmup, then `runs` more times, and returns the `runtime.MemStats.Mallocs` delta divided (as integers) by `runs`. The CLR exposes no malloc counter, so the shim measures allocated **bytes** on the calling thread instead (`GC.GetAllocatedBytesForCurrentThread()` — precise, and inherently thread-scoped, which stands in for the GOMAXPROCS pinning; like Go's, `f` is assumed single-threaded — allocations made by goroutines `f` spawns land on other threads and are not observed). The mapping is deliberately honest rather than count-approximating: **zero maps exactly** (0 bytes ⟺ 0 mallocs — and the stdlib tests that use AllocsPerRun overwhelmingly assert zero, e.g. sort's `TestSearchWrappersDontAlloc` and the strings/bytes no-alloc guards), while a nonzero result is the average allocated bytes per run, floored at 1 so amortized sub-byte-per-run allocation can never masquerade as the exact-zero case. A converted test asserting a specific nonzero *count* therefore diverges as a loud failure in the differential oracle instead of silently passing — the disclosed outcome. (`runs == 0` divides by zero, a runtime-error panic exactly where Go's own integer division panics.) The capability sits in the converter's supported list (`supportedTestCapabilities`, `testConversion.go`), so tests requiring it convert as *included*; guarded by `TestAllocsPerRunCapabilityIsSupported` (converter) and `TestingRuntimeTests.AllocsPerRunMapsZeroExactlyAndReportsBytesWhenAllocating` (shim).

**That no COUNT is available is measured, not assumed, and a nonzero result now says which unit it is in (r56d).** The value the shim returns is rendered by Go's own `"got %v allocs"` format, so a byte figure was reaching the page wearing the word *allocs* — `crypto/internal/nistec`'s row reads `got 21964011.0`, and nothing on it said that was 21 MB rather than 22 million objects. Since a disclosed divergence may never paper over a go2cs-owned defect, an invisible unit at the seam is itself the defect. The survey behind the claim (net9.0/9.0.18, x64) is recorded on the declaration: the whole public `GC` surface exposes byte totals only; `GetAllocatedBytesForCurrentThread` is exact (40.000 B/object over 1, 10, 1e3 and 1e5 allocations of a 40-byte type) yet cannot separate count from size, one `byte[40000]` and 1,000 40-byte objects both reading ≈40,000 B; `GCAllocationTick` is a byte-threshold *sample*, 378 events per 1,000,000 allocations (one per ≈105,820 B); `GCSampledObjectAllocation` — whose `ObjectCountForTypeSample` payload *would* be a count — raises **zero** events through an in-process `EventListener` in every configuration tried (High `0x200000`, Low `0x2000000`, both, and all keywords `0xFFFFFFFFFFFF`, at Verbose and Informational), with the GC keyword's own tick count as the live positive control; `System.Runtime`'s 27 EventCounters offer only `alloc-rate`, bytes per interval; and runtime events reach an in-process listener **asynchronously** — zero visible immediately after the measured loop, settling ≈117 ms later — so no event-derived figure could be returned by a synchronous call anyway. Accordingly a **nonzero** result records its unit once on the running test (`TestExecution.NoteMeasurementUnitOnce`), landing beside the assert's own message and riding the `TestEvent` into `results.json`; the **zero** case is deliberately left silent, because there the two units agree exactly (0 bytes ⟺ 0 allocations) and a test that passes on the zero answer keeps its output byte-identical to before the seam existed. A true count *is* obtainable from go2cs's own runtime rather than the CLR's — golib allocates essentially every Go-semantic object, so counting there mirrors what Go's `Mallocs` already is, a runtime-owned counter rather than a platform facility (proven in r56d: nistec's P256 body allocates 241,077 golib objects per run for its 21,963,547 bytes) — but it is deliberately not taken, since a count that silently omits allocation sites is worse than an honest byte figure and an audited-total census of golib's allocation sites is a design-with-user arc.

The strings suite exposed the two divergence classes this mapping discloses (full analysis:
`docs/phase4/StringsBytes-BlockerMap.md`, *AllocsPerRun divergence analysis*): **count-shape
asserts** (`TestBuilderAllocs` wants exactly 1 malloc; the shim reports bytes, so any nonzero
diverges loudly — by design), and **allocation-profile divergences**, where the zero-shape itself
is unsatisfiable because the managed model allocates where Go's compiler doesn't: an addressed
local (`var b Builder` + pointer-receiver calls) heap-boxes per run where Go stack-allocates
(`TestBuilderGrow`'s growLen=0 leg), and `string(r)` materializes a `byte[]` where Go uses a
stack buffer (`TestIndexRune`). Neither class is a shim defect — a malloc-counting shim would
fail the same asserts — and neither is faked.

**A third class is neither, and must not be filed as either: ELIMINABLE inefficiency.** Because zero
maps exactly, a want-zero assert is faithfully representable — so when one fails, the honest reading is
that the converted code genuinely allocates, and the question is *what*, not *whether the unit is
comparable*. `time`'s `TestUnmarshalTextAllocations` (`got 3784 allocs, want 0`) measured out at **3664
bytes/run** for `Time.UnmarshalText`, and profiling `parseRFC3339` — where nearly all of it lives —
attributes it to two shapes, both in shared machinery and both removable:

| Shape | Cost | Why | Status |
|:--|--:|:--|:--|
| `s[a:b]` on a `string \| []byte`-constrained value | 48 B each | `IByteSeq<T>`'s range indexer returned the **interface**, so the `@string`/`slice<byte>` struct result was boxed | **fixed** — self-referential `IByteSeq<TSelf, T>` |
| `[]byte(s)` on the same (`new slice<byte>(sΔ1)`) | 48 B each | boxed the type-parameter value again to reach the interface | **fixed** — `ToSlice` extension |
| `len(s)` on the same | 48 B each | the `len<T>(IByteSeq<T>)` overload took an interface parameter | **fixed** — `len<TSeq>(TSeq) where TSeq : IByteSeq` |
| `for i, c := range s` over a `slice<T>` | **136 B, fixed** | the range enumerator allocated once per loop, independent of length; the indexed form allocates **0** | **fixed** — struct enumerator |

Six `parseUint` calls at ~232 B each, the closure and delegate for `parseUint` itself (112 B), the
fractional-second scan (~1728 B in the same shapes) and `Date` (~240 B) accounted for the total. None of
it was CLR-necessary: Go monomorphizes the union-constrained generic and stack-allocates all of it, and
nothing here was boxing that a managed model *must* do — it was boxing that the `IByteSeq`
modeling and range lowering happened to do. **So this row was performance work, not a disclosure
candidate** (a disclosure is only for asserts the CLR provably cannot satisfy — see the campaign
charter §5), and it was resolved as such: the range enumerator became a struct (every converted
`for i, v := range s` in the corpus had been paying it), and the union-constraint boxing was removed
wholesale by the self-referential redesign described under
[Allocation-free union-constrained bodies](#allocation-free-union-constrained-bodies) — a
`parseRFC3339`-shaped body over `slice<byte>` measures **0 B/parse** where it measured 720.

**The disclosed-divergence manifest (2026-07-18 ruling — implemented).** These provably
unsatisfiable divergences are disclosed at TEST level, extending the declaration-level
"disclosed-unsupported" vocabulary: an affected package carries a hand-owned, repo-committed
`go2cs_test_disclosures.json` beside its converted sources (never generated — reviewed like
source; deliberately absent from `src/core/.gitignore`'s regenerated-artifact list),
pinning `{name, class, signature, reason}` per divergent test. The `-test-action compare` oracle
(`matchTerminalStatuses`, `testConversion.go`) reclassifies a Go=pass/C#=fail row as
**disclosed-divergent** ONLY when the exact test name is pinned AND the captured C# failure
output contains the pinned signature substring — the converted host attaches each test's
accumulated log text to its terminal event, which is what the signature matches against. The
signature pin is the integrity guard: a pinned test failing with ANY other signature (e.g. an
index-semantics leg regressing) is still a mismatch, a pinned test in any other status pair
(including C#=infrastructure-error) is still a mismatch, and a package with no manifest compares
strictly — sort and utf8 are unaffected. The validation summary discloses the reclassified rows
alongside the excluded declarations (`… 7 disclosed-divergent (alloc-profile), …`), subtracting
them from the validated count, and the nonzero C# host exit the disclosed failures cause is
forgiven only when `go test` itself was clean and every divergence matched its pin (zero
mismatches — a truncated host run surfaces as one-sided rows and stays fatal). An empty
signature (which would substring-match anything) and duplicate names are load-time errors, never
silent no-ops. Guards: `TestDisclosedDivergenceOracle` (signature match discloses / different
signature still fails / no manifest strict / direction+status pairs never widen) and
`TestDisclosureManifestLoading` (absent-file no-op; empty-signature and duplicate rejection).
First users: `bytes` (7 alloc-profile rows) and `strings` (3 alloc-count-semantics + 1
alloc-profile), validating as Phase-4 packages #3 and #4. `unicode/utf16` (package #5) is the first
to reuse the mechanism as a general tool rather than a bytes/strings special case: its lone
`TestAllocationsDecode` asserts `Decode` returns its non-escaping `[]rune` with **zero** allocations —
which Go reaches only through escape analysis (the test guards itself with `testenv.SkipIfOptimizationOff`),
and which the managed runtime provably cannot, since a returned `slice<rune>` is always a heap allocation.
It discloses one `alloc-profile` row (signature `"Decode allocated "`) while `TestDecode` independently
proves the decoded output is correct — the disclosure covers exactly the allocation profile, nothing else.

**The reflect TYPE-RELATION mirrors + Convert (Phase-3 continuation, 2026-07-26).** Go's descriptor
model reaches its type relations by **descriptor specialization**: when `Kind() == Interface` the
`*abi.Type` IS an `interfaceType` allocation, so `implements()` does
`Reinterpret<abi.Type, interfaceType>` and walks `.Methods`; `ptrTo` builds a `ptrType` prototype
through an eface reinterpret; `FieldByName` reinterprets to `structType` and walks `.Fields`. Behind
a **synthesized** descriptor none of that layout exists — the reinterpret produces a struct whose
promoted-embed box is default, and the first read throws from `ж.ValueSlot` ("Cannot get reference
to value…", the encoding/gob type-initializer crash). Reinterpret-specialization is therefore a
class of descriptor reads that can never be honored behind the bridge; each surface severs at its
semantic boundary onto the SAME golib machinery emitted asserts use: `rtype.Implements` /
`rtype.AssignableTo` over `GoReflect.GoImplements` (mirroring the reflectlite increment-1 forms),
`PointerTo` synthesizing the managed `ж<T>` pointer type (canonical via `toType`),
`rtype.FieldByName` over the shared `GoFields` projection (top-level names; the embedded-field
depth search is deferred with a named consumer — a promoted name answers Go's not-found path),
and `Value.Convert` over `GoReflect.TryConvertTo` — THE convertibility relation (the recorded
R-13 remedy), severing the `cvtInt → makeInt → unsafe_New` stub chain (R-14; internal/fmtsort's
package-level `ct()` table). `Value.Cap`/`Value.SetLen` join over the golib container interfaces
(gob's `decodeSlice` probes `Cap() < n` then re-lengths the header — SetLen writes the re-windowed
slice back through the aliased box), and the hand-owned `rtype.Field` stamps the single-hop
`StructField.Index` — an empty Index made the auto `FieldByIndex` return the struct ITSELF, so
gob's `encodeStruct` walked every wireType field as the whole struct. Demonstrated consumers:
encoding/gob's init + Encoder/Decoder engines (a struct round-trips end-to-end), go/token's
`TestSerialization` (FileSet through gob, 31/31), internal/fmtsort (3/3). Registered in
`manualConversionFuncs["reflect"]`; the banked fmtsort/go-token suites are the operational guards.

**The type NAME is a descriptor read too — `reflectlite`'s `rtype.String` (2026-08-02).** The same
class as the specialization reads above, at its quietest. Go's `rtype.String()` is
`t.nameOff(t.Str).Name()`: `Str` is a **name offset** into the linker-built name blob, resolved by
pointer arithmetic from the descriptor's own address. A synthesized descriptor has no blob and no
`Str`, so the mini-bridge's `String()` answered `""` for every type — and answered it **silently**,
because `""` is a legal name for an unnamed Go type, so nothing panicked and no read faulted; the
empty string simply propagated into whatever the caller was building. `context`'s `stringify`
fallback (the arm for a key type with no `String()` method) printed
`context.Background.WithValue(, c1k1)` where Go prints
`context.Background.WithValue(context_test.key1, c1k1)`. That is the failure mode worth recording:
a descriptor read that cannot be honored does not always throw — this one degraded to a
plausible-looking empty field, and only a differential caught it.

`reflect`'s own `rtype.String` has been hand-owned over `GoReflect.GoTypeName` since Phase 1; this
is the identical answer for the mini-bridge (`internal/reflectlite/type_impl.cs`, registered as
`manualConversionFuncs["internal/reflectlite"]["rtype.String"]`), so the full bridge and the mini
bridge cannot disagree about what a type is called. Array dims ride along exactly as on the
`reflect` side — a descriptor that knows its length renders Go's `[N]T` rather than `[]T`. The
managed nesting supplies the package qualifier: `key1` is declared in class
`context_test_package`, stamped `[GoPackage("context_test")]`, so `GoQualifiedName` recovers
`context_test.key1` — including for a `-tests` external test package, whose Go-visible package name
is the stamp's authority rather than the class name.

**Blast radius, measured rather than assumed:** within `reflectlite` only three call sites reach
`String()` — `stringify`/`contextName` (the fix), and the `assignTo` and `elem()` **panic
messages**, which merely become legible. No comparison logic consumes it: `rtype.Name` still
answers `""` for every type because it gates on `HasName()`, which reads the `TFlagNamed` bit that
`synthesizeDescriptor` never sets, and the `haveIdenticalType`/`directlyAssignable` chain that
would consume `Name()` is dead behind the hand-owned `Implements`/`AssignableTo`. `errors` — the
mini-bridge's other consumer — never reaches `String()` at all (`Comparable`/`Kind`/`Implements`/
`AssignableTo`/`Elem`/`Set`/`IsNil` only), and re-validates unchanged at 61/61. Demonstrated
consumer: `context`'s `TestValues`, 36/38 → **37/38** (the remaining failure is `TestAllocs`, the
measured alloc-count disclosure). `rtype.Name` is the recorded next gap of this shape, deliberately
NOT fixed without a consumer that demonstrates it.

**The method COUNT is a descriptor read too — `rtype.NumMethod`, the gate on json's Unmarshaler
discovery (2026-08-02).** The same silent-degradation class as the NAME read above. Go's
`rtype.NumMethod` counts `uncommon()` method tables — trailing descriptor allocations the linker
lays out after the `abi.Type`, which a synthesized descriptor never populates — so it answered
**0 for every concrete type**, and answered it silently, because 0 is the correct count for most
types and nothing downstream faults on it. The consequence hid one hop away: `encoding/json`'s
`indirect()` only ATTEMPTS its `Unmarshaler`/`TextUnmarshaler` interface assert behind
`v.Type().NumMethod() > 0`, so no custom `UnmarshalJSON`/`UnmarshalText` was **ever** dispatched —
every `json.Unmarshal` into `time.Time` fell through to the raw-struct path and died with
`json: cannot unmarshal string into Go value of type time.Time` (time's `TestTimeJSON`; in
`TestUnmarshalInvalidTimes` the miss inverted the failure — `{}` decoded *silently* where
`Time.UnmarshalJSON` rejects it). The marshal side never had the problem: `newTypeEncoder` gates on
`Implements`, hand-owned since the type-relation increment.

Severed at the same semantic boundary as every read of this class: the hand-owned `rtype.NumMethod`
(`reflect/value_impl.cs`) answers over golib `GoReflect.GoMethodCount` →
`TypeExtensions.GoMethodSetCount`, which counts over `GetGoMethodSetCandidates` — the **same
candidate source** the structural probe (`StructurallyImplements`) and the duck-typing shell binder
resolve through, so the NumMethod gate and the interface assert behind it can never disagree about
a method set (a count from any other source could answer 0 for a set the assert would bind, and the
gate would silently re-skip the dispatch this fixes). Candidates are deduplicated by **projected Go
name** — one Go pointer-receiver method reaches the registry in two emitted shapes (the
RecvGenerator's `ж<X>` overload and the original `[GoRecv]` `this ref X` extension) — and
exported-ness is judged Go's way (first rune uppercase) on the projection, after the same leading
collision-marker strip `GoMethodNameMatches` applies. Go's kind split is preserved: an interface
type counts ALL its methods (`GetInterfaceMethodNames`, instance members only — the golib static
`As<T>` helpers stay invisible), the empty interface (`object`) counts 0, a concrete type counts
exported only, with `ж<X>` seeing X's value- AND pointer-receiver methods and a plain X only the
value-receiver ones; an adapter shell answers as the Go dynamic type it stands for, mirroring the
`KindOf`/`ElementType` unwrap (R10). The count is memoized per (element, pointer-ness) and cleared
on assembly load with the candidate cache that feeds it.

One root, four symptom shapes — the guard (`tests/Behavioral/JsonUnmarshalerDispatch`) locks all
four against `go run`: unmarshal into `&t` directly (the Pointer-kind gate), whole-value dispatch
of a non-string JSON value (the error path), a user-declared named type with a pointer-receiver
`UnmarshalJSON` (dispatch is not stdlib-specific), and unmarshal into struct FIELDS (the
`Name() != "" && CanAddr()` → `Addr()` route through the field-alias box). Demonstrated consumer:
time's `TestTimeJSON` and `TestUnmarshalInvalidTimes`. `rtype.Method(i)` stays auto and still reads
the same absent tables — the recorded next gap of this shape: a `NumMethod() > 0` gate now lets a
method-ENUMERATION loop (`for i := range t.NumMethod() { t.Method(i) }`) get further than before,
and the first consumer that walks one demonstrates it.

**…and the count and the WALK are ONE increment — `Type.Method(i)`, `Value.Method(i)`,
`MethodByName` (2026-08-03).** The paragraph above shipped alone and was **reverted**: the
recorded successor gap arrived one session later, on the very next all-package sweep. `math/rand`
and `math/rand/v2`'s `TestRegress` enumerate every generator method — `typ.Method(i).Name`,
`rv.Method(i)`, `mv.Type()`'s `NumIn`/`In`, `mv.Call(args)` — against a golden output table, and
both went from validated to `panic: reflect: Method index out of range`. The lesson is general and
worth stating as a rule: **a truthful count is a PROMISE that the table behind it can be indexed.**
While `NumMethod` answered 0 the enumeration loops were unreachable and their auto `Method(i)`
(which reads the same absent `uncommon()` tables) could not be observed; making the count truthful
is exactly what made them reachable. A descriptor read and the gate in front of it belong in one
increment.

The whole table is now ONE list — `TypeExtensions.GetGoMethodSetEntries` — and `NumMethod` is its
`.Count`, so a size and an order can no longer be derived separately and disagree. It is built over
`GetGoMethodSetCandidates`, the same candidate source `StructurallyImplements` and `AdapterBinder`
resolve through, then: deduplicated by projected Go name (keeping the shape a delegate can bind —
a `[GoRecv] this ref X` receiver cannot be a `Func<>` parameter, and the RecvGenerator's `ж<X>`
overload always sits beside it), exported-only for a concrete type, and **sorted ORDINALLY by Go
method name**, which is Go's own method-table order (verified against `go run`: a promoted embedded
method sorts *in place*, it is not appended).

A **method value is an ordinary bound delegate**, and that is the design's whole economy. Go
carries `v.Method(i)` as the receiver's own Value plus a `flagMethod` bit with the index packed
into the flag, then rebuilds the signature (`typeSlow`) and re-resolves the receiver
(`methodReceiver`) on every use — all descriptor reads. The bridge instead BINDS the receiver into
a managed delegate at `Method(i)` time, so the result is a Kind-Func Value and everything
downstream is reuse rather than new surface: `mv.Type()` is the ordinary canonical Type of a
delegate, `NumIn`/`In`/`NumOut`/`Out` are the existing `TryFuncShape` readers, and `mv.Call(args)`
is the existing `Value.Call` **unchanged** — with the receiver already absent from the signature,
which is precisely Go's method-value contract. `Type.Method(i).Func` is the same delegate UNBOUND
(receiver first), Go's contract for the type side, and `Value.MethodByName` needs no hand-own at
all: it composes the hand-owned `rtype.MethodByName` and `Value.Method`.

Binding is **expression-compiled, and that is not a preference**: `Delegate.CreateDelegate(type,
firstArgument, method)` cannot close over a VALUE-type first argument (measured — `ArgumentException`)
and Go value receivers are by-value structs, so the closed-delegate form fails for every
value-receiver method, `time.Time`'s entire method set included. Each `MethodInfo` is compiled once
into a `Func<object?, Delegate>` factory (a nested lambda — the outer takes the receiver, the inner
IS the bound Go-signature delegate), so a bind costs a closure allocation, not a compile. A
value-receiver method reached through `*X` is handed a COPY of the pointee, Go's rule.

**A `this object` extension method is golib plumbing, never a Go method** — and filtering it is a
correctness fix, not tidiness. `GetGoMethodSetCandidates`' assignability safety net (there for
promotion and base relationships) is satisfied by EVERY type when the receiver is `object`, so
`TypeExtensions.TryCastAsInteger(this object, out ulong)` was entering every type's method table —
and entering it **nondeterministically**, because the candidate scan is redone whenever a late
assembly load clears the caches: the same binary reported `NumMethod` **4 or 6 for the same type**
depending only on which assemblies had loaded by the first read (it reproduced under the behavioral
runner's redirected stdout and not under a console). That also means the shipped-then-reverted
count was wrong in a way nothing could observe. It is filtered in the method-TABLE builder rather
than in the shared candidate source, whose admission rule the duck-typing assert and the shell
binder also read: a Go METHOD SET is a stricter question than "could this extension method dispatch
on this value?".

Guard: `tests/Behavioral/ReflectMethodTableWalk` locks the surface against `go run` — count/walk
agreement and sorted order, a value type's set excluding pointer-receiver methods, a bound
pointer-receiver call MUTATING the receiver across calls, a value-receiver method bound through a
pointer, a no-result method, `MethodByName` round-tripping to the same index (and both absent-name
forms), the unbound `Method.Func` called receiver-first, promoted-embed ordering, and an interface
table whose method value dispatches to the dynamic value. Demonstrated consumers: `math/rand`
**43/43** and `math/rand/v2` **36/36** re-validate at their exact banked counts with `TestRegress`
now genuinely walking (the converted bridge reports `*rand.Rand NumMethod: 16` in Go's order and
`Intn(1000000000) = 526058514`, matching `go run` — where before the pair it reported 0 and the
test passed VACUOUSLY, executing none of its 320 golden comparisons); `time` goes 146 → **148**
pass of 159 as the two increment-6 JSON rows re-land. Recorded next gaps of this shape: `MakeFunc`,
variadic `Call`/`CallSlice`, and `reflectlite`'s `rtype.Name`.

**A ZERO test is a descriptor read too, and this one had degraded to a CONSTANT — `Value.IsZero`,
`Value.Grow`, and the named-string `Len` (2026-08-03).** Go's `IsZero` is three reads over flat
memory: an `Equal` function pointer compared against the shared `zeroVal` buffer, a
`TFlagRegularMemory` all-bits-zero scan, and — when the value is not `flagIndir` — plain
`v.ptr == nil`. A synthesized descriptor populates none of them, and the bridge never populates
`v.ptr` or `flagIndir` at all, so the Array and Struct arms both fell straight to that last test and
answered **true for every array and every struct, whatever it held**. Measured against `go run`
before the fix: `[2]uint8{1,2}`, `NA{1,2}`, `inner{N:1}`, `outer{P:&n}` — every one of them
`IsZero=true` in C# and `false` in Go. This is the `""`-type-name and `NumMethod`-0 family again,
and the worst-behaved member of it so far: `true` is the correct answer for the zero value of the
same type, so nothing faults and nothing looks wrong.

A **fourth** read failed independently and had to land with it. `IsZero`'s String arm is
`v.Len() == 0`, and `Len` answered through the golib container interfaces — a named slice is an
`IArray`, a named map an `IMap` — but a `type NS string` wrapper implements none of them, so it fell
to the `0` default. Every non-empty named string therefore reported itself both length-0 and zero.
`String()` had always unwrapped such a wrapper; `Len` now does the same, gated on Kind String.
That pairing is the increment-6 rule in its second form: **`IsZero`'s String arm is a GATE on `Len`,
so the gate and the read behind it are one increment** — fixing the arms without `Len` would have
left named strings silently zero, and fixing `Len` without the arms would have changed nothing.

The managed `IsZero` is Go's own recursive definition with the memory shortcuts *removed*: a
composite is zero exactly when every element (Array) or field (Struct) is, scalars test against
their zero, and the nilable kinds are `IsNil`. That is precisely the walk the shortcuts stand in for
— Go falls back to it itself when a type is not comparable and not regular-memory — so it needs no
descriptor state beyond `Index`/`Field`/`NumField`, which the bridge already answers. Go's blank-field
skip (`Name != "_"`) is preserved.

`Value.Grow` shares the root and the remedy shape: it reads a `*unsafeheader.Slice` off the same
never-populated `v.ptr`, so it **nil-deref'd for every caller** — `reflect.ValueOf(&s).Elem().Grow(1)`
on a `[]byte` prints `4 8` in Go and panicked here. It is now an ordinary managed reallocation
(golib `GoReflect.GrowSlice`) written back through the aliased box exactly as `SetLen` does, coerced
into a named slice wrapper's slot through the single convertibility relation. Two details are
load-bearing: growth **within** the existing capacity writes nothing at all, because Go reaches
`growslice` only past the capacity and a spurious write would detach any other view still sharing the
backing store; and the capacity landed on is unspecified in Go (its `growslice` rounds to a size
class), so only `len+n` is guaranteed and the guard test asserts `cap >= n`, never an exact figure.

Guard: `tests/Behavioral/ReflectZeroAndGrow` pins all four against `go run` — raw and named strings,
raw and named arrays (zero and non-zero, including an array OF named strings), the nil-vs-empty
distinction for slices and maps, structs made non-zero through a nested named-string field alone,
zeroness reached through an interface, and Grow from a nil slice / within capacity / past it /
with `Grow(0)`. Demonstrated consumer: `encoding/gob`, whose `gobEncodeOpFor` skips a field on
`!state.sendZero && v.IsZero()` — so the encoder was omitting non-zero named-string and array fields
from the wire entirely, visible as `v = "", want "forty-two"` on the value fields while the pointer
fields of the same type passed.

**Rooted and deliberately NOT landed: `MapType().Hasher` and `Key.Equal` cannot be honored at all.**
The remaining `unique`/`net` wall is a map descriptor whose `Hasher`/`Key`/`Elem` are unpopulated, so
`concurrent.NewHashTrieMap`'s delegate construction fails on the first field it touches. Populating
them looks like the same shape as the reads above and **is not**, because the contract differs in
kind: `Hasher(unsafe.Pointer, uintptr) uintptr` must hash *the value at an address*, and the address
the call site produces cannot name a managed value. Measured, three ways: two boxes holding equal
`@string` values necessarily have **different** addresses (so an address-derived hash can never make
`unique.Make("hello")` agree with itself — the package's entire purpose); a box whose pointee
contains a reference has no pinnable slot and its address **moved across a forced GC**; and the
`unsafe.Pointer` handed to the delegate carries no link back to its source box by construction, its
constructor taking a `uintptr`. The key and elem *types* ARE recoverable from the descriptor's
carried `System.Type` — but populating those alone would be actively worse than the present failure:
`Key.Equal` is the comparability SIGNAL (a pointer-identity compare, not a value compare), so a
half-populated descriptor turns a loud construction failure into a map that silently mislays every
key. That is the increment-6 lesson inverted — **a descriptor field whose read cannot be honored must
not be populated to look truthful** — and it is why this row is handed on rooted rather than half
landed. The remedy is one layer down and outside this arc's files: `internal/concurrent.HashTrieMap`
is a managed-referent raw-metal case whose *contract* (a concurrent map over comparable `K`) the CLR
answers natively while its *mechanism* (hash the bytes at an address) it cannot, so it wants a
hand-owned `_impl.cs` on the `sync.Mutex` precedent.

**Pointer order tokens — `Value.Pointer()`/`UnsafePointer()` (golib `PointerOrderToken`).** Go
programs order pointers *arithmetically* (`cmp.Compare(a.Pointer(), b.Pointer())` —
internal/fmtsort's map-key ordering of `*T`/`chan`/`unsafe.Pointer` keys), so the bridge's token
must be more than stable-per-instance: **equal Go pointers must token equally, and same-storage
element pointers must order by element index** (Go's `&a[0] < &a[1] < &a[2]`). `INilPointer` gains
the `PointerOrderToken` surface (a DIM default of per-instance identity): `ж<T>` answers nil → 0,
a native alias → its real address, an array/slice-element reference → the canonical backing
storage's identity in the high bits with the ABSOLUTE element index below (the same
`CanonicalElement` reduction pointer equality uses), a struct-field reference → source identity ×
field-identity token, a heap box → the referent's identity; `unsafe.Pointer` (whose VALUE is a
real pinned address) overrides with the address itself; `channel<T>` answers through its shared
core's identity, so every struct copy/boxing of one channel reports ONE token — what makes
sorting channels by `Pointer()` self-consistent (fmtsort's `makeChans` pre-sorts by the same
key). Tokens are order keys consistent with pointer equality, never an identity substitute
(distinct storages can collide); generated named pointer/channel wrappers keep the DIM default —
a recorded fidelity residual with no consumer. The banked fmtsort suite (TestCompare/TestOrder)
is the operational guard.

**…except a TYPE DESCRIPTOR pointer, which orders by the type's NAME (2026-08-10).** The same
`Value.Pointer()` carries one ordering that is visible in ordinary program output rather than only
in a map of pointers: fmtsort's `reflect.Interface` arm orders interface-kinded map keys by dynamic
type, and it does that by comparing the two descriptors as pointers —
`compare(reflect.ValueOf(a.Elem().Type()), reflect.ValueOf(b.Elem().Type()))` recurses into the
`ΔPointer` arm — so this token *is* the printed order of `fmt.Println(map[I]int{…})`. Go answers with
the linker's type-section address, which is unspecified by its own admission (fmtsort's
`TestInterface`: "the relative ordering of types is unspecified", asserting only that same-type keys
group) and is not a function of anything the managed side can see. The identity-hash fallback above is
*worse* than unspecified for this case — CoreCLR draws an object's identity hash from a per-thread
PRNG, so the token is fixed per build but unrelated to the type, and the printed order flips whenever
an unrelated edit shifts how many hashes are drawn first. `reflectPointerToken` therefore routes a
`ж<rtype>`/`ж<abi.Type>` through `typeDescriptorOrderToken`, which packs the leading `IntPtr.Size`
bytes of the descriptor's Go name (the one `Type.String()` prints) big-endian, so comparing tokens
arithmetically compares the names lexically: **types that print alike token alike, and types that
print differently order by that printed name** — stable across builds, runs and unrelated edits.
Names agreeing over the whole packed prefix tie and fall through to fmtsort's concrete-value arm
(Go's own "no good answer" `-1`, settled deterministically by `SortStableFunc`'s stability); matching
Go's layout order for three or more key types is not on offer and would not be a property Go
promises. Guarded by the banked fmtsort suite (TestInterface's grouping) and the
`InterfaceInheritance` behavioral test's output comparison, which is what caught the PRNG model
landing tails. Full derivation:
[`docs/phase4/DESIGN-reflection-bridge.md`](phase4/DESIGN-reflection-bridge.md).

**EXPORTEDNESS is a descriptor read too, and the value side had been right about it all along —
`StructField.PkgPath` (2026-08-11).** `reflect.StructField.IsExported()` is nothing but
`f.PkgPath == ""`, and Go fills `PkgPath` with the declaring package's import path for an
unexported field (`type.go`: `if !p.Name.IsExported() { f.PkgPath = t.PkgPath.Name() }`). The
hand-owned `rtype.Field(i)` left it unset — the field's own comment said so, on the reading that no
truthful read backed it — so `IsExported()` answered **true for every field of every converted
struct**. Silent, like every member of this family: `""` is the correct `PkgPath` for the exported
fields that are most fields, so nothing faulted and nothing looked wrong.

The consequence is a **guard that can never fire**. `encoding/asn1` opens both its struct arms —
`parseField` and `makeField` — with

```go
for i := 0; i < structType.NumField(); i++ {
    if !structType.Field(i).IsExported() {
        return StructuralError{"struct contains unexported fields"}
    }
}
```

so `Marshal(unexported{X: 5, y: 1})` returned a **nil** error where Go returns that structural
error, and `Unmarshal` ran straight past the refusal into `parseField(val.Field(i), …)` on the
unexported field, where `SetInt` reached `mustBeAssignable` and panicked
(`TestUnexportedStructField`). Note what that panic proves: the two halves of the read-only model
had degraded **independently**. `Value.Field` already stamped `flagStickyRO` for an unexported
field — from `GoReflect.GoFields`, the same projection the type side walks — so `CanSet()`/
`CanInterface()` were correct and the write was correctly refused; it was only the TYPE-side
descriptor that had no answer, which is why a package that *probes* settability got no warning
while a package that simply *writes* got a clean Go-shaped panic. `PkgPath` now derives from the
same projection's `Exported` bit plus `GoReflect.GoPackagePath` (the package identity the managed
nesting carries, already `rtype.PkgPath`'s source), so a probe of the type and a write through the
value cannot disagree about a field.

Two neighbouring `StructField` members stay unpopulated, for two different reasons worth keeping
apart. **`Offset`** is the r39d rule — a descriptor field whose read cannot be honored must not be
populated to look truthful: a Go byte offset exists to be added to a data pointer, and managed
storage has no such pointer. (`abi.StructType` does populate `Offset`, and correctly: its consumers
— `unique`'s clone sequencer, `internal/reflectlite` — read it as layout metadata, never as an
address to walk.) **`Anonymous`** is the opposite case: it IS knowable, since an embedded field
arrives through golib's promoted-embed box hop, but no measured consumer demands it and the
recorded next gap of that shape is larger — go2cs-gen emits the promoted-embed backing box AFTER
the declared fields, so `struct{X; y; Inner; inner; Ptr}` walks as `X, y, Ptr, Inner, inner` here
where Go walks it in declaration order. Field ORDER and `Anonymous` want one increment together,
with a consumer that demonstrates them.

Guard: `tests/Behavioral/ReflectUnexportedFieldFlags`, byte-identical to `go run` — the indexed
walk's `IsExported`/`PkgPath`/`Tag`, `FieldByName` carrying the same flags (including the blank
field, which Go also reports unexported), a field-for-field assertion that the type side and the
value side AGREE (`v.Field(i).CanSet() == t.Field(i).IsExported()`), and the consumer shape itself:
a decoder that probes before writing must be able to refuse with a returned error rather than a
panic. Demonstrated consumer: `encoding/asn1`'s `TestUnexportedStructField`.

**A func PARAMETER is the one position an array's LENGTH cannot be recovered from — `[GoArrayDims]`
(2026-08-11).** A Go array's length is part of its type, and it is the one part the managed emission
cannot carry: `[32]byte` renders as golib `array<byte>`, and C# has no const generic parameter to
hold the 32. The bridge has always answered that by recovering the dimension from a live source
instead, and the two it had covered every position that mattered — a VALUE measures itself
(`GoReflect.ArrayDimsOfValue`), and a struct FIELD reads it off a cached zero instance of the
declaring struct, because the converter emits the dimension as a field initializer (`= new(32)`)
that the generated parameterless constructor runs.

A func parameter has neither. There is no value at a type-only position, no initializer to read,
and the emitted delegate type is a bare `Func<array<byte>, bool>` that `func([32]byte) bool` and
`func([64]byte) bool` **share**. So `reflect.TypeOf(f).In(0)` answered a dims-less array descriptor:
`Len()` 0 and `String()` `"[]uint8"` — which does not even read as an array — and `reflect.New`/
`reflect.Zero` of it built a **zero-length** array. `testing/quick` is the consumer that shows what
that costs, because its generator allocates the argument from the parameter type alone
(`v := reflect.New(concrete).Elem()`, then `for i := 0; i < v.Len(); i++`): every property test over
a fixed-size array ran against the EMPTY value. `crypto/internal/edwards25519`'s
`TestScalarSetCanonicalBytes` indexed `in[len(in)-1]` and panicked with `index out of range [-1]
with length 0`; its sibling `TestScalarSetUniformBytes` reported `failed on input [0]uint8{}`, which
names the empty array outright.

The datum therefore has to live at the parameter, and it does: the converter stamps
`[GoArrayDims(32)]` there (outermost dimension first — `[2][3]int` ⇒ `[GoArrayDims(2, 3)]`), from
the single `generateParametersSignature` all three signature builders share, so declarations,
methods, func literals, func types and interface methods are all covered by one emission point.

```go
f1 := func(in [32]byte, sc Scalar) bool { … }      // edwards25519's scalar_test.go
```
```csharp
var f1 = ([GoArrayDims(32)] array<byte> @in, Scalar sc) => { … };
```

`GoReflect.FuncParamDims` reads it back off the delegate **INSTANCE** —
`Delegate.Method.GetParameters()`, which resolves to the real declaration for every shape go2cs
emits (a declared func used as a method group, a non-capturing lambda, a capturing lambda's
display-class method, a natural-typed lambda, a local function) — and `abi.TypeOf` stamps it as
descriptor cargo beside `arrayDims`, so `reflect.Type.In(i)` hands out an array type that knows its
length. The cargo joins BOTH interning keys (`abi.descriptorDimsKey`, shared with reflect's
`canonType`) for the reason the array dims are already in them: `func([32]byte) bool` and
`func([64]byte) bool` are distinct Go types over one managed delegate type, so interning them
together would let whichever arrived first answer `In(0).Len()` for both.

Three boundaries are deliberate. A **defined** (named) array type is not stamped — its managed form
is a generated wrapper, not `array<T>`, so dims cargo could not be consumed even if carried — while
an **alias** for an array is, because a Go alias *is* its target type. **Result** dims are not
carried at all: a multi-result Go func returns a `ValueTuple`, which has no per-element attribute
position, and no measured consumer reads `Out(i).Len()`. And a delegate whose target method's
parameter list does not line up one-for-one with `Invoke`'s — an open instance delegate carries the
receiver as an extra leading parameter, and the bridge's own method values are expression-compiled
closures with no attributes — is answered `null` rather than mis-indexed, which is the r39d rule in
its usual form: a dims-less descriptor is a state the bridge already handles, a mis-indexed one is
not.

**The same datum two hops further out — a METHOD's parameters, and a `*[N]T` (2026-08-14).**
`net/rpc` is the consumer that found both hops missing, and it found them the hard way. Its server
allocates every reply argument from the method type alone —
`replyv = reflect.New(mtype.ReplyType.Elem())`, where `ReplyType` came from `mtype.In(2)` — so a
service method declared `func (BuiltinTypes) Array(i int, reply *[1]int) error` needs the `1` to
survive two hops the func-value route does not have:

1. **The func type comes from the method TABLE, not from a delegate instance.**
   `reflect.Type.Method(i).Type` is synthesized in `value_impl.cs` from `GoMethodFuncType` over the
   `MethodInfo`'s parameters, and nothing in that path ever holds a delegate for
   `GoReflect.FuncParamDims` to read. `GoReflect.MethodParamDims(t, i)` reads the same
   `[GoArrayDims]` stamps straight off those `ParameterInfo`s, and `Method(i)` carries them as the
   descriptor's `funcParamDims`. It owes no arity guard, unlike the delegate route: the delegate
   type is synthesized FROM that parameter list, receiver included, so the indices line up with
   `In(i)` by construction — which is Go's own shape for a method type, receiver first.
2. **The array sits behind a POINTER.** A callee that writes its result through a parameter takes a
   `*T`, so the type-only position whose length must survive is the *pointee's*. The converter now
   stamps a parameter's pointee dims (one hop; that is all a Go signature spells here), and a
   pointer descriptor's dims pass through `Elem()` **unshifted** — there is nothing else they could
   describe, a pointer having no length of its own — while an array's dims still shift, its element
   consuming the outer one. The stamp had to be added to `visitFuncDecl`'s REBUILT signature path as
   well, and that is where it actually fires: having a pointer parameter is itself what triggers the
   rebuild, so a `*[N]T` parameter never reaches `generateParametersSignature` at all.

Without either hop, `reflect.New(In(2).Elem())` built a zero-length array and the callee's first
write panicked `index out of range [0] with length 0` — on an rpc goroutine, which took the entire
converted-test host down with it (`net/rpc/jsonrpc`'s `TestBuiltinTypes`; the same run's other eight
tests then recorded no verdict at all, which is what made the panic read as three failures instead
of one).

Guard: `tests/Behavioral/ReflectFuncArrayParamDims`, byte-identical to `go run` — `In(0)`'s
`String`/`Kind`/`Len` and the `New`/`Zero` lengths across a literal, a multi-parameter literal, a
nested `[2][3]int`, a declared func used as a value and a func with no array parameter at all; the
distinctness of `[32]byte` and `[64]byte` as `In(0)` types; the inner dimension surviving `Elem()`;
the struct-field route still answering; and quick's generation loop in miniature (allocate from the
parameter type, fill through `Index(i).Set`, `Call`), so a zero-length synthesis shows up as the
callee's wrong answer rather than a silent pass. It carries rpc's shape too, since 2026-08-14:
`Method(i).Type.In(2).Elem().Len()` for a `*[3]int` reply, `reflect.New` of it, and the callee
writing through the pointer — a length the descriptor does not know is not merely mis-reported
there, it PANICS. Converter unit guard: `TestGoArrayDimsAttribute` (both shapes, including the
boundaries: a pointer to a DEFINED array type, to a slice, and a double pointer are all unstamped).
Corpus footprint of the 2026-08-14 half, measured by re-transpiling all 592 behavioral packages:
**5 declarations in 5 files, one line each** — four `*[N]T` parameters, plus one `[4]byte` VALUE
parameter (`DeferTypelessReturns`' `first`) that had been silently unstamped all along, its function
taking the rebuilt path because it heap-boxes. Nothing else moved.

**The `reflect.DeepEqual` bridge (`reflect/deepequal_impl.cs`, Phase-4 — blocker-map R5).** Go's
`deepValueEqual` keys its cycle-detection `visited` map on the values' internal data words (`v.ptr` /
`v.pointer()`) — eface addresses the managed bridge never populates — so the first slice/map/pointer
comparison converted the null `unsafe.Pointer` slot and NREd (`deepequal.cs:74` → unsafe `op_Implicit`;
first operational hits: strings and bytes `TestSplit`/`TestSplitAfter`). The converter skips **only**
`deepValueEqual` (`manualConversionFuncs["reflect"]`; `DeepEqual` itself stays auto — its body only
touches the bridged `ValueOf`/`Type`/`AreEqual`), and the hand-owned form re-implements the recursion
arm-for-arm with Go's switch over the bridge's **boxed** values: elementwise arrays/slices with the
`[]byte` fast path (`Span.SequenceEqual` standing in for `bytealg.Equal`); the nil-vs-empty slice
distinction read from the REAL backing (`m_array` null ⟺ the golib `default` = nil — the public
`slice<T>.Source` materializes a detached copy, so the impl reads `m_array`/`m_low` via cached
reflection, the same pattern as the bridge's `IsNull`/`Value` property reads); struct fields via
`goStructFields`; maps compared key-by-key through the backing `Dictionary` (same-map identity
short-circuits — Go's "same map object" rule — and a missing key fails exactly like Go's invalid
`MapIndex`); pointer identity as `ж<T>`-box reference equality (one box per variable ≙ Go's address
equality, so the same slice/pointer is deeply equal to itself even holding NaN); IEEE float semantics
(C# `==` on `double` — NaN ≠ NaN, like Go); funcs never deeply equal unless both nil. Cycle detection
mirrors Go's `hard()` step on managed identity: (pointer box | map `Dictionary` | slice backing array
+ `Low`) pairs in a reference-identity set, added before recursing so in-progress checks are assumed
true — self-referential structures terminate.

**Two defects the guard could not see (2026-07-26), one of them the guard itself.** The recursion read
the Value's raw `boxed` field, but an **ADDRESSABLE** Value — a slice element, an array element, a
struct field — carries its value behind `addrBox` (the `ж<T>` it aliases) and leaves `boxed` **null**,
so every such read saw null on *both* sides and the identity short-circuits fired:
`DeepEqual([][]byte{[]byte("ab")}, [][]byte{[]byte("ac")})` was **true** — each element's backing read
as null, matched "same initial entry of the same underlying array", and the elementwise walk never
ran. The recursion now reads `live` (identical to `boxed` for a non-addressable Value) once per side
and threads it through every arm. Second, `mapBacking` probed for a field assignable to the
**non-generic** `IDictionary`, which a generated named-map wrapper does not have — it holds a
`map<K,V>` **struct**, whose own backing store is one level deeper — so both sides of a *named*-map
comparison resolved to null, `ReferenceEquals(m1, m2)` matched them as "the same map object", and two
named maps of equal length were deeply equal **regardless of contents** (`identityRoot` was blind the
same way, so a named-map cycle was never detected either). The probe now takes that second step.

Neither showed up because the guard's own comparison was **vacuous**: it printed with the builtin
`println`, which writes to **stderr**, and the runners compare stderr by FIRST LINE only (Go's panic
reports carry a machine-specific stack trace, so a full stderr comparison can never match). 46 of its
47 assertions were unchecked. The guard now prints with `fmt.Println` — stdout, compared in full — and
is extended to 47 cases: the original slice/struct/map/pointer/cycle set plus named maps (equal,
differing value, differing key, differing length, self, nil-vs-empty), a named map as a struct field, a
named map OF slices, and nil-map-key parity on both plain and named `map[any]int`. Counter-proven: pre-
fix the `[][]byte` case and six named-map cases printed the wrong answer. (`Solitaire` is the only other
output-compared project that prints solely through `println`; its comparison is vacuous for the same
reason — recorded as a follow-up, together with tightening the runners' stderr rule to a full compare
whenever the exit code is not a panic.) The guard project references the
full-conversion `reflect` (the baseline stub has none): its `Directory.Build.targets` redirects the
emitted `core\reflect` reference to `core\reflect` — the Performance-suite pattern for
settings the per-transpile csproj regeneration would otherwise clobber. Surfaced by the guard's output
comparison: golib's `print`/`println` now render a `bool` as gc's runtime printer does (`true`/`false`,
not the BCL `True`/`False`).

**A third defect: the FUNC arm asked the wrong question (2026-07-31).** Go's rule is "func values are
deeply equal if both are nil; otherwise they are not deeply equal," and the arm implemented only the
second half — an unconditional `return false` — on the reasoning that two nil funcs would already have
matched through the `invalid == invalid` rule at the top. That reasoning holds for a **top-level** nil
func boxed as `any` (the null object, whose Value is invalid), and for nothing else: a nil func reached
as a struct **field** — or a slice/array element, or a map value — is typed by its *static* func type,
which by design makes it a **VALID** nil Value (see `Value.Field`). So any two structs carrying a nil
func field were reported unequal, and asking `IsNil()` of both values is the fix. The observable form is
a whole-struct comparison that can never succeed no matter how carefully the test normalizes the rest:
`compress/flate`'s `TestWriterReset` nils `fill`/`step`/`bulkHasher`/`bestSpeed`, copies `hashMatch`, and
substitutes `tokens`/`window` precisely so `DeepEqual` can compare everything else — and it failed at
**all ten** compression levels. That was the package's only failing test; with the arm corrected
`compress/flate` validates **64 / 64**. The diagnosis is worth recording because every *individual*
field compared equal (through `Field(i).Interface()`, which re-boxes and so re-enters the invalid-value
path) while the enclosing struct did not — the discrepancy between the two is what named the arm. The
`DeepEqual` guard gains nine cases: nil-func struct fields equal, one side non-nil, a struct with a
non-nil func field compared to **itself** (Go says not equal), differing non-func fields, nil funcs as
slice elements and as map values, and the top-level nil/non-nil pair that the old arm did handle.
Counter-proven by neutering the arm back to `return false`: the guard's output comparison fails.

**The Windows directory-entry walk (`os/dir_windows_impl.cs`, Phase-4 — os operational).** Go's
`(*File).readdir` walks the buffer `GetFileInformationByHandleEx` fills by REINTERPRETING it as a Go
struct — `info := (*windows.FILE_ID_BOTH_DIR_INFO)(entry)`, then
`unsafe.Slice(&info.FileName[0], info.FileNameLength/2)`. That struct is **managed-referent**: its
inline Go arrays (`ShortName [12]uint16`, the variable-length trailing `FileName [1]uint16`) convert to
golib `array<uint16>` OBJECT references — 8 bytes each where the OS wrote 24 and 2 bytes inline — so
the managed layout does not describe the buffer bytes at all. `&info.FileName[0]` addressed a
zero-length array (`System.IndexOutOfRangeException` on the FIRST directory read: `path/filepath.Glob`,
`os.ReadDir`, `os.File.Readdirnames`, and every test that walks a testdata directory), the fields after
`ShortName` sit at the wrong offsets, and merely *copying* the reinterpreted struct hands the GC a
fabricated object reference. No converter or golib change can rescue this — a managed array reference
can never be laid out like an inline OS array — so it is the *raw metal on non-native types* arm of the
[conversion fork](../src/archived/Baseline-vs-FullConversion.md): the declaration is hand-owned.

Scope is one declaration, via the type-level registry (`manualConversionFuncs["os"]["File.readdir"]`);
everything else in `dir_windows.go` (`dirInfo.init`/`close`, the pool, `dirEntry`) stays auto and keeps
receiving converter improvements. The hand-owned form decodes the two entry layouts straight out of the
byte slice at their documented offsets and never materializes a managed struct over OS memory — every
read is an ordinary **bounds-checked managed** slice read, so there is no pointer, no pinning and no
`unsafe` block in the file (a short or truncated buffer surfaces as an index panic rather than reading
past the OS data). Offsets come from the **Go** declarations in `internal/syscall/windows`, which match
the buffer bytes for every field Go reads: Go widens Win32's `CCHAR ShortNameLength` to `uint32`, which
shifts only `ShortName` — a field neither Go nor the impl reads — while `FileID` (96) and `FileName`
(104) land identically either way. The auto `newFileStatFromFileIDBothDirInfo` /
`newFileStatFromFileFullDirInfo` remain emitted but are now unreachable (their parameter *is* the
unusable reinterpret); the impl builds the `fileStat` from the same offsets.
⚠ The registry key is name-keyed per package, so it also matches `os.(*File).readdir` in `dir_unix.go`
— a `-platforms linux/amd64` conversion of `os` would drop its (perfectly convertible) unix readdir.
Same platform caveat as runtime's `lock_sema` entries. No behavioral guard is expressible: the baseline
`src/core` has no `os` package, so the guard is the operational one — `go/doc/comment`'s `TestTestdata`
(`filepath.Glob` over `testdata/`) went from *zero subtests ran* to 54 enumerated.

**The testing shim's compile-only benchmark surface and `CoverMode` (`core/testing/testing.cs`).** Capability-excluded test and benchmark declarations still **compile** — exclusion gates the run registry, not emission — so every member their bodies reference must exist even though the code never executes (a broken emission inside an excluded test blocks the whole package build; see the strings/bytes blocker map, B6). The `B` surface (`N`, `Run`, `ReportAllocs`, `SetBytes`, `ResetTimer`, `StartTimer`, `StopTimer`, `Errorf`, `Fatal`, `Fatalf`) is therefore compile-only: safe non-throwing no-ops, with the params-taking members carrying explicit `ж<B>` overloads exactly as `T`'s do (ref-like `params` Spans are outside the RecvGenerator's synthesis). `testing.CoverMode()` returns `""` — not a stub-lie but Go's exact coverage-off value: the sole caller across the strings/bytes suites (strings' TestIndexRune) branches on `CoverMode() == ""` and so takes the same path as an uncovered `go test` run. Guarded by `TestingRuntimeTests.BenchmarkCompileSurfaceIsNoOpAndCoverModeReportsCoverageOff`, which compile-references every member through both receiver shapes and asserts the coverage-off semantic — removing any member fails the suite at build.

**The same rule extends to `testing.F` (2026-07-20).** A `Fuzz*` declaration is classified disclosed-unsupported in the manifest exactly as a benchmark is (`testConversion.go` already emitted the `fuzz`/"deferred to Phase 4D" entry), but its converted body still compiles into the test assembly — and `F` simply did not exist, so math/big's `func FuzzExpMont(f *testing.F)` (nat_test.go) failed the whole package build with CS0426 *the type name 'F' does not exist in the type 'testing_package'*. `F` now mirrors `B`: a compile-only struct whose members are safe non-throwing no-ops, with explicit `ж<F>` overloads on the params-taking members. Its member set is Go 1.23's full public surface for `*testing.F` — the `TB` members it inherits from the embedded `common`, plus its own `Add` and `Fuzz` — declared complete under the same anti-drift rule as `TB` above rather than trimmed to today's callers. `Fuzz` takes a **`System.Delegate`**: a Go fuzz target's signature is arbitrary (`*testing.T` followed by the fuzzed argument types), and the converted body is an explicitly-typed lambda, so C# infers its natural `Action<…>` and converts — no per-arity overload set is needed. Nothing is invoked and no seed corpus is retained, because there is no fuzzing engine to consume either. This is not math/big-specific: roughly seventeen stdlib packages ship fuzz targets (archive/tar, archive/zip, compress/gzip, encoding/csv, encoding/json, html, image/{gif,jpeg,png}, net/netip, time, syscall, …), every one of which would hit the identical build blocker.

### Realizing an asm-backed arch layer with managed hardware intrinsics

Hand-owning an asm-backed declaration does not have to mean stubbing it. Where .NET exposes the *same* instructions the `.s` file issues — via `System.Runtime.Intrinsics` — the architecture layer can be ported for real, and the converted package gains genuine hardware acceleration rather than a fallback. **`hash/crc32` is the first of its kind (2026-07-24) and sets the pattern.**

Go's `crc32_amd64.go` declares three functions with no body — `castagnoliSSE42`, `castagnoliSSE42Triple` (the SSE4.2 `CRC32` instruction) and `ieeeCLMUL` (PCLMULQDQ carry-less multiply folding) — implemented in `crc32_amd64.s`. Converted literally they become bodyless `partial`s that the [`PartialStubGenerator`](#source-generators) fills with `NotImplementedException`, so `crc32.go` always took the slicing-by-8 fallback and the package's own `TestArchIEEE`/`TestArchCastagnoli` **skipped**. `crc32_amd64.cs` is hand-owned (`[module: go.GoManualConversion]`, whole-file) and the three functions are transcribed against `System.Runtime.Intrinsics.X86` — `Sse42.X64.Crc32` for the CRC32B/W/L/Q chain, `Pclmulqdq.CarrylessMultiply` + `Sse2` for the fold and Barrett reduction, `Sse41.Extract` for the final `PEXTRD`. Every other declaration in the file is the converted output verbatim.

Three rules make this a repeatable recipe rather than a one-off:

* **Probe capabilities LOCALLY; never flip `internal/cpu`'s global flags.** The Go guards read `cpu.X86.HasSSE42` / `HasPCLMULQDQ` / `HasSSE41`. Those flags are shared by **every** converted package's arch path, and the rest of those arch layers are still throwing stubs — setting them centrally would trade each package's working portable fallback for a `NotImplementedException`. The hand-owned file instead defines its own predicates over the `.IsSupported` properties of exactly the instruction sets it uses (`Sse42.X64.IsSupported`; `Pclmulqdq.IsSupported && Sse41.IsSupported`) and `archAvailable*` returns those. The claim then stays precisely true — "these instructions are available to *this* code" — and `internal/cpu` is untouched. When a probe is false, `crc32.go`'s own `archAvailable*` branch falls back to slicing-by-8, which is exactly how Go degrades on an architecture with no arch implementation.
* **Port the `.s` file, label for label, and say so.** The fold constants are the `.s` `DATA` pairs verbatim; a 128-bit load puts the offset-0 quadword in the low half, so `Vector128.Create(offset0, offset8)` reproduces each register image exactly. `PCLMULQDQ`'s `imm8` maps directly onto `CarrylessMultiply`'s `control` (bit 0 selects the *left* operand's quadword, bit 4 the *right*'s), so `PCLMULQDQ $0x11, X0, X1` is `CarrylessMultiply(x1, x0, 0x11)`. Each ported block carries the `.s` label it transcribes (`aligned`, `less_than_8`, `loopback64`, `remain64`, `remain16`, `finish`) so the two can be diffed by eye at upgrade time.
* **Divergences are deliberate, documented in place, and result-neutral.** The `.s` file walks the buffer to an 8-byte boundary before its `CRC32Q` loop; the managed port drops that step. It exists to align the loads, it cannot change the answer (CRC is a pure function of the initial value and the byte *sequence*, and the `CRC(I, ABC)` combining identity holds for any split), and it has no managed counterpart — a slice's backing array can be moved by the GC, so an address observed here is not an address the loads keep. Likewise the `.s` triple loop is a `DECQ`/`JNZ` do-while that would wrap on `rounds == 0`; the managed form is a counted loop, identical for every real input.

Note the csproj constraint this recipe was written under: the converter **regenerates each package's `.csproj` on every transpile**, and it sets `AllowUnsafeBlocks` from `usesUnsafeCode` alone -- which was `false` here, so a hand-owned file could not use `byte*`/`fixed`/`stackalloc` and any setting added by hand was clobbered on the next run. That is no longer the only option (a file may now [declare the requirement](#a-hand-owned-file-can-declare-that-it-needs-unsafe)), but this recipe is still the better answer where it applies: it needs no compiler flag at all. The loads go through `MemoryMarshal.GetReference(p.ToSpan())` plus `Unsafe.ReadUnaligned<T>` / `Unsafe.Add`, which need no compiler flag and read the slice's real backing window (offset included) with no copy.

The payoff is that the package's *own* test suite becomes the correctness oracle, which is what makes this pattern safe to repeat: `TestArchIEEE` and `TestArchCastagnoli` cross-check the intrinsics against the portable slicing-by-8 implementation over randomized buffers at 46 lengths chosen to straddle the `168*3=504` and `1344*3=4032` cutoffs, and enabling the arch path also routes `TestGolden`/`TestGoldenMarshal` through it against known vectors. All 8 `hash/crc32` Test functions match `go test`, with nothing skipped and no disclosed divergences (Phase-4 validated package; see `docs/Roadmap.md`). Confirmed by positive control — corrupting a fold constant and the `CRC32B` tail turns exactly the arch-dependent tests red while the portable-only ones stay green.

### A cross-package `//go:linkname` PULL emits a forwarder, not a throwing stub

A bodyless function carrying `//go:linkname <local> <pkgpath>.<func>` (a three-field directive naming another package) is a **PULL** — the function has no body of its own and links to another package's (often unexported) symbol. `golang.org/x/sys/windows`'s `LazyDLL`/`LazyProc` reach the Go runtime's DLL loaders this way:

```go
//go:linkname syscall_loadlibrary syscall.loadlibrary
func syscall_loadlibrary(filename *uint16) (handle Handle, err Errno)
```

Left as an ordinary bodyless declaration, it would emit a `partial` that the [`PartialStubGenerator`](#source-generators) turns into a **throwing** stub — dead DLL loading. The converter (`visitFuncDecl.go`) instead recognizes the directive and emits a **forwarder body** that calls the target, bridging any nominal `num:uintptr` type difference through `uintptr` (the linked signatures are structurally identical, so a mismatch is only between two such types):

```csharp
internal static (ΔHandle handle, Errno err) syscall_loadlibrary(ж<uint16> filename) {
    var (ᴛ1, ᴛ2) = syscall.loadlibrary(filename);
    return ((ΔHandle)(uintptr)ᴛ1, (Errno)(uintptr)ᴛ2);
}
```

Pointer/slice/string parameters (`filename`) pass through unchanged (the same golib type on both sides); a **uintptr-kind** parameter is passed `(uintptr)p` and a uintptr-kind result returned `(LocalType)(uintptr)r`. The bridge is scoped to uintptr-kind types (`uintptr` and named types whose underlying type is `uintptr`, e.g. `Handle`/`Errno`) — precisely the case where the two linked signatures name the same value under two different *nominal* C# types. A **sized** integer (`int32`/`int64`/…) is the same C# type on both sides and passes through bare; routing it through `uintptr` instead narrows it on 32-bit and does not even bind (`(uintptr)timeout` handed to an `int64` parameter — sync's `runtime.blockUntilEmptyFinalizerQueue` pull, CS1503).

The target alias is **whatever the importing file actually emitted** for the target package, not the bare last path segment: an explicit `import r "runtime"` is looked up in the file's recorded import aliases, and otherwise the canonical alias is taken through the same collision-rename the import machinery applies. That rename is not hypothetical — a file that pulls from `runtime` while any `go.runtime.*` namespace is in scope emits `using Δruntime = runtime_package;`, because a bare `runtime` alias would bind the **namespace**, and a forwarder spelled `runtime.<fn>` is then CS0234 (sync's `oncefunc_test.go`).

**Forwarding is gated on an explicit whitelist of hand-implemented targets** (`linknameForwardTargets` — `syscall.loadlibrary`/`loadsystemlibrary`/`getprocaddress`, the native P/Invokes in `core/syscall/dll_windows.cs`, plus `runtime.blockUntilEmptyFinalizerQueue`, the finalizer-queue drain that sync's and runtime's own tests pull and that `mfinal.cs` answers with `GC.WaitForPendingFinalizers`). This is not optional prudence: a linkname target is **indistinguishable at conversion time** from any other bodyless assembly/intrinsic Go function — `syscall.loadlibrary` and `runtime.reflectcall` are *both* bodyless `//go:` asm in Go — so only the whitelisted targets are known to have a real C# implementation to call. Every other linkname pull stays a bodyless stub, the pre-forwarder behavior: a method-receiver PUSH (`//go:linkname X reflect.(*rtype).Align`, reflect's `badlinkname.go` "pushes linknames of the methods"), a same-package pull (`//go:linkname unusedIfaceIndir reflect.ifaceIndir` inside reflect), and an unimplemented intrinsic (`//go:linkname call runtime.reflectcall`) would each otherwise emit an uncompilable forwarder (a nonexistent `reflect.(*rtype)`/`runtime.reflectcall` member, or a package alias that doesn't exist for the package's own name). Extend the whitelist when a new native linkname target gains a hand-written C# implementation — and remember the **accessibility** half: Go's linkname crosses the package boundary the way C# `public` does, so an unexported target (which the exported-ness rule emits `internal`) is invisible to the forwarder in the pulling *assembly* and must be widened where it is declared (`runtime.blockUntilEmptyFinalizerQueue` is `public` in the hand-owned `mfinal.cs` for exactly this reason). Guarded by `TestRecurseLinknameForwarder` (asserts the whitelisted `syscall.loadlibrary` forwarder body + the uintptr result bridge, and that a non-whitelisted `runtime.reflectcall` target stays a stub).

**A whitelisted target may be ORDINARY CONVERTED GO, and then the converter widens it itself.** Every
entry above answers a *hand-written* body. `time.registerLoadFromEmbeddedTZData` is the first that does
not: `time/zoneinfo_read.go` declares it with a real Go body, `time` authorizes the pull with the
matching one-arg handle, and `time/tzdata`'s `init()` calls it — the only edge between the two packages,
since `time/tzdata` imports `errors`, `syscall` and `unsafe` and never `time`. Two consequences follow
from that, and both are general:

* **The accessibility half is no longer a hand-own's responsibility.** `packageFuncAccess` emits a
  package-level free function `public` when it carries a one-arg linkname handle **and** its
  `<pkgpath>.<name>` is a forward target. The handle alone is deliberately not enough — Go 1.23 carries
  340 of them outside `cmd/`, and publicizing every one would widen the corpus's whole surface for pulls
  that are never emitted. The whitelist is the converter's own record of which pulls actually become a
  call, so gating on it moves exactly the symbols that need to move.
* **The forwarder must be able to name a package the file never imports.** The pull queues the target's
  path for a **project reference** (as the var-pull arm already did) and, when the file carries no import
  spec for it, emits the call **fully qualified** — `go.time_package.registerLoadFromEmbeddedTZData(…)`,
  which resolves inside `namespace go;` with no alias. A bare `time.` would have bound the `go.time`
  CHILD namespace — tzdata's own — rather than the package class (CS0234).

Until this landed, a blank `import _ "time/tzdata"` threw `NotImplementedException` out of a **module
initializer** and took the whole program down before `main`, which is exactly what the blank-import
`init` forcing made reachable. It also turns out to be what lets `time`'s test suite load zone data at
all: with tzdata registered, `loadLocation` falls back to the embedded database when the GOROOT
`lib/time/zoneinfo.zip` path a test hard-codes does not resolve from the C# host's working directory.

**A linkname target implemented as a golib BUILTIN forwards to a bare, unqualified call** (`linknameForwardBuiltins`, a sibling map of Go linkname target → golib builtin name). Some Go compiler intrinsics live in `runtime` and are linked into another package by symbol, but their go2cs implementation is a golib builtin — in scope UNQUALIFIED via each converted project's `using static go.builtin`, so the forwarder emits `<builtin>(args)` with no package qualifier (an empty alias is the sentinel `writeLinknameForwarder` reads to drop the `<alias>.` prefix). The canonical case is **`maps.Clone`**: Go implements its worker as `runtime.mapclone` (`//go:linkname mapclone maps.clone`) and the `maps` package pulls it as a bodyless `func clone(m any) any` carrying `//go:linkname clone maps.clone` — a *same-package-named* target (`maps.clone`) whose real definition is elsewhere, so it is neither a native whitelist target nor a normal pull, and was left a **throwing** `PartialStubGenerator` stub (every `maps.Clone`/`maps.Copy`/`maps.DeleteFunc` test threw `NotImplementedException: clone: external (assembly or cgo) function is not implemented`). It now forwards:

```csharp
internal static any clone(any m) {
    return mapclone(m);
}
```

`builtin.mapclone(any m)` is Go's `runtime.mapclone` at golib level: it recovers the boxed map's concrete key/value types through `IMap.CloneMap()` (a default interface method on `IMap<TKey, TValue>`, so both the concrete `map<K, V>` and the generated named-map wrappers get it with no source-generator change — no reflection) and returns a fresh `map<K, V>` populated from the source's entries. The clone's backing `Dictionary` is **independent** — Go's shallow clone (keys/values copied by ordinary assignment), so mutating the clone never touches the original — and a nil map clones to nil. This is what carries the `maps` package to full Phase-4 validation (14/14 tests vs `go test`; the 6 Clone/Copy/DeleteFunc tests previously threw). Extend `linknameForwardBuiltins` when another linkname intrinsic gains a golib builtin. Guarded by the `MapCloneLinkname` behavioral test — the exact `//go:linkname clone maps.clone` shape in a `main` package, cloning a `map[string]int`, mutating the clone (overwrite/add/delete) and asserting the original is unchanged, output-compared vs `go run`; proven to emit the throwing stub against the un-fixed converter.

### A cross-package `//go:linkname` PUSH resolves per recorded disposition — forwarder or announced panic

The PULL above is one of two directions, and the converter long handled only that one. A **PUSH** runs the
other way: the *defining* package carries the body and names ANOTHER package's declaration as the symbol it
defines, while the consuming side is an ordinary bodyless func under a **one-argument** `//go:linkname
<thisFunc>` handle. `runtime/mgc.go` pushes into `unique`, `runtime/mheap.go` into `internal/weak`:

```go
// unique/handle.go — the CONSUMER: bodyless, one-arg handle (Go's authorization for the push)
//go:linkname runtime_registerUniqueMapCleanup
func runtime_registerUniqueMapCleanup(cleanup func())

// runtime/mgc.go — the PUSHER: an ordinary body naming the consumer's symbol
//go:linkname unique_runtime_registerUniqueMapCleanup unique.runtime_registerUniqueMapCleanup
func unique_runtime_registerUniqueMapCleanup(f func()) { … }
```

Nothing linked the two, so the consumer's declaration fell to the [`PartialStubGenerator`](#source-generators)
and threw on first call — `unique.Make`'s `setupMake.Do(registerCleanup)` took `net/netip`'s initializer and
`encoding/gob`'s `TestNetIP` with it. The consuming side now resolves to one of **two** emissions, chosen by a
disposition recorded per pair in `linknamePushTargets` (`linknameOperations.go`), keyed by the consumer's own
`<pkgPath>.<symbol>`:

```csharp
// unique/handle.cs — FORWARDED: the pushed body is ordinary converted Go, so call it
//go:linkname runtime_registerUniqueMapCleanup
internal static void runtime_registerUniqueMapCleanup(Action cleanup) {
    Δruntime.unique_runtime_registerUniqueMapCleanup(cleanup);
}

// internal/weak/pointer.cs — UNHONORABLE: announce the pair, never fabricate a body
//go:linkname runtime_registerWeakPointer
internal static @unsafe.Pointer runtime_registerWeakPointer(@unsafe.Pointer _) {
    throw panic("go2cs: //go:linkname push runtime.internal_weak_runtime_registerWeakPointer -> internal/weak.runtime_registerWeakPointer is not honored: the pushed body walks mheap_ span metadata the managed model does not populate; internal/weak wants a hand-owned managed weak reference");
}
```

The accessibility half mirrors the pull's: a forwarder calls the pushing definition **across an assembly
boundary**, so `packageFuncAccess` emits that definition `public` from the reverse index `linknamePushSources`.
The pull arm requires the target's own one-arg handle as Go's authorization; a push carries its authorization on
the **pushing** side instead, so this arm reads the registry alone. Reaching the
pusher can be the only edge to its package, so the path is queued for a project reference exactly as the pull
queues its target (`linknameTargetAlias`, now shared by both arms, also resolves the file's actual using-alias —
`unique/handle.cs` spells `Δruntime`, not `runtime`).

**The consumer has TWO shapes, and a row records which one it is** (`bareDecl`, added 2026-08-08). The example
above is the **handle** shape — a one-arg `//go:linkname <thisFunc>` above the bodyless declaration, Go's modern
way of opening a symbol to a push, used by `unique` and `internal/weak`. The standard library also pushes into a
**bare** shape that predates that convention: a bodyless declaration with *no directive at all*, carrying only a
prose comment saying where the body lives. `syscall` and `os` still use it, and it is every bit as legal:

```go
// syscall/env_unix.go — the BARE consumer: no directive of its own
func runtime_envs() []string // in package runtime

// runtime/runtime.go — the PUSHER, where this pair's only directive lives
//go:linkname syscall_runtime_envs syscall.runtime_envs
func syscall_runtime_envs() []string { return append([]string{}, envs...) }
```

The matcher originally required the handle unconditionally (it returned early on `funcDecl.Doc == nil`, before
even reading the registry), so the bare shape could never resolve — it fell to the `PartialStubGenerator`, and
because `syscall.envs` is a package-level var *initialized* from that call, the throw came out of `syscall`'s
type initializer and took `os.init()`, and every Linux program that so much as touches `fmt`, with it. Nothing
on Windows could see it: `env_unix.go` is `//go:build unix || (js && wasm) || plan9 || wasip1`, so that
declaration does not exist in the Windows corpus, and the registry's original census was taken against a
Windows-only emission.

The shape is **recorded per row rather than inferred**, so the match fails closed in both directions: a handle
row will not forward a bare declaration, a bare row will not forward one carrying a handle, and a two-arg
directive (a PULL — a different mechanism) is rejected by both arms. Neither shape is verifiable from the
consumer's own syntax, which is the whole reason this registry is curated, so the shape belongs in the same
recorded judgment as the disposition (`linknamePushDeclMatches`, `visitFuncDecl.go`). The `syscall.runtime_envs`
row forwards honorably rather than plausibly: `runtime.envs` really is populated in the managed model, by the
hand-owned `runtime/goenvs_impl.cs` module initializer, so the forwarder hands back the real process
environment. Guarded two ways — `TestRecurseLinknamePush` carries the 2x2 over shape (`unauthorized` and `bare`
are *syntactically identical* and differ only in what their row records, which is what proves the recorded shape
is what decides), and `TestLinknamePushRegistryMatchesGoSource` checks every row against the real Go source in
GOROOT: the consumer's declaration exists, is bodyless, matches the recorded shape, and the pushing side really
does carry the two-arg directive the row vouches for.

**Why a curated registry rather than general detection.** The same reason the pull whitelist exists, plus one
more that is structural: **the converter never sees the pushing package's directives while converting the
consumer.** A package is converted from its own syntax; its dependencies contribute types, not comments — and
the pusher is not even guaranteed to be a dependency. So a bodyless one-arg-handle func is indistinguishable at
conversion time from an ordinary assembly stub, and the disposition has to be recorded. Go 1.23 carries ~200
pushes outside `cmd/`; the converted corpus exposes **eleven** of them as bodyless one-arg-handle declarations,
and linking those wholesale would be a regression dressed as a feature — `time`'s timer trio is already answered
by `time_impl.cs` and a converter-emitted body would collide with it, while `internal/syscall/windows`'s
`stdcall` wrappers and `internal/coverage/cfile`'s linker-section walk push bodies the managed model cannot run
at all. Each entry is therefore a judgment, recorded with its reasoning beside the key.

**The unhonorable arm is the inverse-atomic rule in emission form.** `internal/weak`'s two halves reach the span
allocator (`registerWeakPointer` → `getOrAddWeakHandle` → `spanOfHeap` → `throw("getWeakHandle on invalid
pointer")`; `makeStrongFromWeak` re-derives an object pointer from a heap address). A forwarder there would
either fault or — far worse — hand back a plausible-looking pointer derived from garbage, which is exactly the
"populate a field whose read cannot be honored" move the rule forbids. The panic is deliberately a **Go panic
naming both halves of the pair and the reason**, not the generator's `NotImplementedException`: the declaration
is not an unimplemented assembly stub, it is a real Go contract this conversion has decided it cannot keep, and
the first caller to hit it should land on the hand-own the row actually needs. That hand-own has since
LANDED — see [*`internal/weak.Pointer`*](#internalweakpointer--the-clr-already-has-weak-references-so-the-runtime-handle-becomes-one)
below — so these two rows no longer describe the deployed corpus, where `pointer.cs` is marked and never
regenerated; they describe what a conversion into a root that does *not* already carry the hand-own emits,
which must still be the loud pair. The reason string now names the file to reach for.

By contrast `unique`'s pushed body is **ordinary converted Go** — it makes a `chan struct{}` and starts a
goroutine that drains it and calls the callback — so the managed model runs the real thing: the registration
succeeds and the cleanup goroutine parks on the channel. Nothing signals it, because the converted runtime's
`clearpools()` is driven by Go's own GC, which does not run. That is Go's OWN behavior for a program whose GC
never fires (the intern map simply keeps its entries), not a fabricated answer — the distinction the two arms
turn on.

Guarded by `TestRecurseLinknamePush`, which runs the real `-recurse` converter over a two-package module fixture
with its dispositions injected for the test's duration, and asserts all four arms: the forwarder body, the
pushing definition's publicization, the pair-naming panic, and that both an unregistered handle and a registered
declaration *without* a handle stay bodyless stubs. (A behavioral project cannot reach this mechanism — the
registry is keyed by the consumer's import path, and the behavioral harness converts each package alone, so
neither a fixture path nor cross-package discovery is available to it. The pull forwarder is guarded at the same
layer, and for the same reason, by `TestRecurseLinknameForwarder`.)

**A push whose consumer is an EXTERNAL TEST package needs no new machinery — the key simply spells the test
package path** (2026-08-12). `runtime/metrics.go` pushes its test-only name reader into the package's own
test suite:

```go
// runtime/metrics/description_test.go — the CONSUMER, in package metrics_test (handle shape)
//go:linkname runtime_readMetricNames
func runtime_readMetricNames() []string

// runtime/metrics.go (package runtime) — the PUSHER
//go:linkname readMetricNames runtime/metrics_test.runtime_readMetricNames
func readMetricNames() []string { … }
```

The `-tests` conversion's `convertTestVariant` resets package state from the external variant's own
`packages.Package`, so `currentPackagePath` is already `runtime/metrics_test` while its files convert — the
registry row `"runtime/metrics_test.runtime_readMetricNames"` matches through the exact code path every
production consumer uses, and the emitted forwarder
(`return global::go.runtime_package.readMetricNames();`) replaced the throwing stub that held the package's
`TestNames` at 1 of 2. Any production package pushing into its own `_test` package takes the same shape. The
registry guard learned the location half: an external test package has no GOROOT directory of its own, so
`TestLinknamePushRegistryMatchesGoSource` resolves `<base>_test` to the base package's directory and scans
its `_test.go` files (only those whose package clause carries the `_test` suffix — in-package test files
belong to `<base>` itself).

The same package's `Read` entry point is the registry's second **measured** unhonorable row, and the
measurement is worth recording: a forwarder was tried first, and the pushed body ran — through the
hand-owned managed `metricsLock` — all the way to `readMetricsLocked`'s slice-header reconstruct
(`*(*[]metricSample)(unsafe.Pointer(&sl))` over a raw first-element address), which no managed pointer can
alias: the fabricated slice read garbage `@string` names. The deployed corpus routes around the seam instead
of through it — `runtime/metrics/sample.cs` is hand-owned and its `Read` marshals names in and computed
`(kind, scalar, pointer)` out through the public `runtime.readMetricsManaged` shim (`managed_impl.cs`, the
`registerPoolCleanup` pattern), preserving the batch semantics while the metrics table and every compute
closure stay auto-converted — so the `runtime_readMetrics` row exists for a conversion into a root WITHOUT
the hand-own, where the bodyless declaration reappears and must announce the wall rather than fabricate past
it. `runtime/metrics` validates 2 of 2 on this arrangement.

### `internal/concurrent.HashTrieMap` — a managed map where Go seeds itself from `MapType().Hasher`

`internal/concurrent` is the whole of `unique`'s storage, and `unique` is `net/netip`'s address interner —
so this one type sits in front of `unique`'s entire suite, `net`'s last package-initializer root and
`encoding/gob`'s `TestNetIP`. Go 1.23's implementation is a lock-free hash-trie, and **every bit of its
behavior comes from one runtime descriptor read**:

```go
func NewHashTrieMap[K, V comparable]() *HashTrieMap[K, V] {
	var m map[K]V
	mapType := abi.TypeOf(m).MapType()
	ht := &HashTrieMap[K, V]{
		root: newIndirectNode[K, V](nil), keyHash: mapType.Hasher,
		keyEqual: mapType.Key.Equal, valEqual: mapType.Elem.Equal,
		seed: uintptr(rand.Uint64()),
	}
	return ht
}
```

`Hasher` is a raw function pointer into the hashing machinery the compiler emits for `map[K]V`;
`Key.Equal`/`Elem.Equal` are its matching bit-compare thunks. All three take `unsafe.Pointer`s and mean
*"hash / compare the bytes AT this address"* — and **the managed reflection bridge cannot honor that
contract**. An address in the CLR names no value: two boxes holding equal strings sit at different
addresses, and a pointee containing references moves across a GC. An address-derived hash would therefore
stop `unique.Make("hello")` agreeing with itself, which is the exact inverse of the package's purpose.

Populating `Hasher` anyway — with anything plausible — is barred by the **inverse of the atomic rule**: a
descriptor field whose read cannot be honored must stay EMPTY, because a half-populated descriptor converts
a loud construction failure into a map that is silently wrong. (Reflection increment 8 rooted the row there
and reported it *not landable in the bridge*.) So the literal conversion compiles and can never run:
`NewHashTrieMap` threw inside the package initializer of every `unique` consumer, taking `net/netip` and
every dependent with it.

**The ruling (2026-08-03) is the `sync` precedent applied one level up: hand-own the whole file, and keep
the SEMANTICS rather than the mechanism.** `sync`'s Mutex/RWMutex/WaitGroup are reimplemented on
`SemaphoreSlim`/monitors because Go's sleeping semaphore is co-designed with the state machine and cannot be
emulated; here the coupling is to the descriptor surface instead of to the scheduler, but the fork is the
same one — the raw-metal arm of the S1 fork (see
[`Baseline-vs-FullConversion.md`](../src/archived/Baseline-vs-FullConversion.md)). `src/core/internal/concurrent/hashtriemap.cs`
carries `[module: go.GoManualConversion]` and contains **no trie at all**. The exported API and its
concurrency contract are preserved exactly; the store is a `ConcurrentDictionary`, whose guarantees line up
member for member:

| Go member | Managed mechanism | Semantic note |
|:--|:--|:--|
| `NewHashTrieMap[K, V]()` | `Ꮡ(new HashTrieMap<K, V>(store: new mapStore<K, V>()))` | the store is a CLASS, so a by-value copy of the struct shares one map — exactly what Go's `root *indirect[K,V]` pointer gives |
| `(*HashTrieMap).Load(key)` | `TryGetValue`; miss returns `(*new(V), false)` as `@new<V>().ValueSlot` | `[GoRecv]`, so the RecvGenerator still mints the `ж<…>` overload `unique` binds |
| `(*HashTrieMap).LoadOrStore(key, value)` | `TryGetValue` → `TryAdd` retry loop | exactly one caller of a racing set observes `loaded == false`; `GetOrAdd` is a single call but cannot report WHICH outcome occurred, and `unique.Make` depends on that answer |
| `(*HashTrieMap).CompareAndDelete(key, old)` | `ContainsKey` gate → `TryRemove(KeyValuePair)` | the pair overload is an atomic compare-and-remove under `EqualityComparer<V>.Default`; the gate reproduces Go's order (a missing key returns false *without* comparing values) |
| `(*HashTrieMap).All()` | closure over the store's enumerator | ConcurrentDictionary's enumeration is **weakly consistent** — never throws on concurrent mutation, visits each live key once, promises no order — which is Go's documented contract verbatim, and is what lets `unique`'s cleanup pass `CompareAndDelete` while it walks |
| zero `HashTrieMap` | `storeOf` lazily installs the store (`Interlocked.CompareExchange`) | Go 1.23's zero value is unusable (nil `root`/`keyHash`); nothing depends on that panic, and the same `gateOf` idiom `sync.Mutex` uses removes a whole class of null dereference |
| `keyHash` + `keyEqual` | `EqualityComparer<K>.Default` | see below — verified to BE Go's `==` for every key shape the corpus interns |
| `valEqual` | `EqualityComparer<V>.Default`, guarded by `mustBeComparable` | Go's value comparison panics for an INTERFACE `V` holding an uncomparable dynamic type (`V comparable` admits `any` since Go 1.20, moving the check to run time); the guard mirrors that panic instead of letting the comparer answer a question Go refuses to. Inert for a non-interface `V`, resolved once per instantiation |

**The equality/hash bridge is the correctness question, and it was measured, not reasoned.** Go hashes and
compares keys by K's own `==`; the managed implementation uses `EqualityComparer<K>.Default`. For every key
shape the converted corpus actually interns these agree:

* **`ж<T>`** (`unique`'s own `map[*abi.Type]any`) implements `IEquatable<ж<T>>` as pointer IDENTITY with a
  matching identity hash, and `abi.TypeFor<T>()` interns one descriptor box per `System.Type` — so one Go
  type always presents one key, and a second `TypeFor` call finds the first call's entry.
* **A `[GoType]` struct** — `net/netip`'s `addrDetail{isV6 bool; zoneV6 string}`, the shape `unique`
  actually interns — carries a generated field-wise `Equals` over `==` plus a `HashCode.Combine` of the
  same fields, which is Go's struct `==` exactly. It does **not** implement `IEquatable<T>`, so
  `EqualityComparer<T>.Default` routes through the `object` override; that lands on the same comparison, at
  the cost of one box per lookup.
* **`@string`** compares and hashes by CONTENT, as Go's string `==` does — verified with two keys built
  from distinct backing storage.

**Two further walls sit BEHIND this one**, both uncovered by making `unique` reachable for the first time
and both outside this file:

1. ~~**A cross-assembly `//go:linkname` PUSH never links.**~~ **CLOSED** — see
   [*A cross-package `//go:linkname` PUSH resolves per recorded disposition*](#a-cross-package-golinkname-push-resolves-per-recorded-disposition--forwarder-or-announced-panic)
   above. The forwarder machinery handled the PULL direction only (a bodyless declaration naming another
   package's symbol); `runtime` pushes the other way —
   `//go:linkname unique_runtime_registerUniqueMapCleanup unique.runtime_registerUniqueMapCleanup`
   (`mgc.go`), `//go:linkname internal_weak_runtime_registerWeakPointer internal/weak.runtime_registerWeakPointer`
   (`mheap.go`) — and the *consuming* package's bodyless declaration was left for the
   [`PartialStubGenerator`](#source-generators) to fill with `NotImplementedException`. `unique`'s
   registration now FORWARDS to runtime's converted body; `internal/weak`'s two halves stay unlinked **by
   ruling** (the pushed bodies walk `mheap_` span metadata) and announce the linkname pair rather than
   fabricate one. The remedy they name — a hand-owned managed weak reference — has since landed; see
   [*`internal/weak.Pointer`*](#internalweakpointer--the-clr-already-has-weak-references-so-the-runtime-handle-becomes-one)
   below.
2. **`abi.TypeFor<T>()` is silently WRONG for an interface `T`.** Its non-interface branch returns an
   interned descriptor; the interface branch is `TypeOf((*T)(nil)).Elem()`, and `Type.Elem()` for
   `Kind == Pointer` reinterprets the descriptor as a `PtrType` (`Ꮡt.Reinterpret<Type, PtrType>()`) and
   reads `.Elem` — which under the managed layout lands on the descriptor's `Equal` field. `TypeFor<any>()`
   and `TypeFor<error>()` therefore return a `System.Func<unsafe.Pointer, unsafe.Pointer, bool>`, not a
   `ж<abi.Type>` at all. Shared generics let that object be *stored* into
   `ConcurrentDictionary<ж<abi.Type>, any>` without a cast check, and the first real key comparison then
   dispatches `IEquatable<ж<abi.Type>>.Equals` on a delegate → `EntryPointNotFoundException`. The old trie
   never dispatched anything on a key's runtime type (it compared raw addresses through `keyEqual`), which
   is why a corpus-wide bridge defect could hide behind it. **Not hardened against here on purpose** —
   tolerating a type-unsafe key would be the same "plausible but fake" move the inverse-atomic rule
   forbids; the loud failure is the correct behavior and the fix belongs in `abi`.

**Guarding measurement.** `encoding/gob` holds at **95 of 106** and `TestNetIP`'s root moves from
`NewHashTrieMap` → `ArgumentException: Delegate to an instance method cannot have null 'this'` to the
linkname stub above (no row regresses; `TestNetIP` is the only gob row whose closure reaches `unique` at
all). `unique` itself goes **0 → 1 of 19** and, more usefully, stops being a one-root wall: its 15 identical
`TypeInitializationException` rows resolve into five distinct downstream roots (the two linkname pushes, a
`GCHandle: Object contains references` on `abi.Escape`, an `IndexOutOfRangeException` in `makeCloneSeq`'s
`slice<T>` enumeration, and the `TypeFor` hole above). The hand-owned file is also its package's only Go
file, which makes `internal/concurrent` fully hand-owned — see
[`Baseline-vs-FullConversion.md`](../src/archived/Baseline-vs-FullConversion.md) for what that does to the package's
`.csproj`/`package_info.cs`/`README.md`, and for the seeded-reconvert proof in both directions.

### The Linux syscall bottom — ONE libc P/Invoke, and why `r2` is exact rather than approximate

Go reaches the Linux kernel through a single assembly function. `internal/runtime/syscall/asm_linux_amd64.s` loads the call number into `RAX` and `a1..a6` into `RDI, RSI, RDX, R10, R8, R9`, executes `SYSCALL`, and reports `RAX` and `RDX`:

```go
// internal/runtime/syscall/syscall_linux.go — no body, no linkname, no Go anywhere
func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr)
```

Everything funnels through it: `syscall`'s `RawSyscall`/`RawSyscall6`/`Syscall`/`Syscall6`, every generated wrapper in `zsyscall_linux_amd64.cs` (open, read, write, close, stat, getrlimit, …), and this package's own `EpollCreate1`/`EpollWait`/`EpollCtl`/`Eventfd`, which is how `internal/poll` and the netpoller reach the kernel. Converted, it is a bodyless partial, so the [`PartialStubGenerator`](#source-generators) filled it with a throw — and because `syscall`'s own `init()` calls `Getrlimit(RLIMIT_NOFILE)` before `os` is usable, that one throw stopped every Linux program before `fmt.Println` could emit a byte.

The hand-own (`core/internal/runtime/syscall/linux/syscall_linux_impl.cs`) binds **glibc's `syscall(2)`** rather than reproducing the instruction — a user ruling, taken over the alternative of mapping each wrapper onto a .NET API. One P/Invoke lights the whole generated surface at once; the per-call alternative is N hand-owns, each independently guessing at semantics the kernel already defines exactly.

```csharp
[DllImport("libc", EntryPoint = "syscall", SetLastError = true)]
private static extern nint libc_syscall(nint number, nint a1, nint a2, nint a3, nint a4, nint a5, nint a6);
```

Three details separate a faithful binding from a plausible one. Each was **measured on linux/amd64** (glibc 2.35, .NET 9) rather than argued from the ABI documents, because each is exactly the kind of claim that reads as obviously true and is expensive when it is not:

* **The variadic.** C declares `long syscall(long number, ...)`; this declares seven fixed native ints. That is correct under SysV AMD64 — integer-class variadic arguments ride the same registers as fixed ones, with the seventh spilling to the stack, which is precisely where glibc's hand-written `syscall.S` reads `a6`. Proven with a real six-argument call: `mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)` returned a live mapping that `munmap` then released. (`AL`, which a true variadic call sets to the vector-register count, is unused by `syscall.S`.)
* **`r2`.** Go's contract returns `RDX`, which libc's wrapper cannot hand back — the reason the [run-layer finding](phase4/FINDING-linux-run-layer.md) listed it as an open question. It does not need to: the Linux x86-64 convention clobbers only `RCX` and `R11`, so `RDX` still holds what entered the kernel, and the asm's `MOVQ DX, BX` observes `a3` unchanged. Returning `a3` is therefore not a stand-in for `r2` on this architecture — it *is* `r2`. Probed under the real Go runtime: `syscall.Syscall6(SYS_GETPID, …, a3=0xDEADBEEF, …)` returns `r2=0xdeadbeef`. The failure path zeroes `r2`, which the shim mirrors.
* **`errno`.** libc collapses the kernel's whole `[-4095, -1]` error band to a `-1` return and reports the positive errno out of band — the same number Go's asm produces by negating the raw return — and `SetLastError` lets the CLR capture it before managed code can perturb it. Probed: `openat(AT_FDCWD, NULL)` returns `-1` with `Marshal.GetLastPInvokeError() == 14` (EFAULT).

**One divergence is disclosed rather than papered over:** a syscall that legitimately returns `-1` as a *success* value is indistinguishable from a failure through libc, and would report a stale errno. Go's asm has no such ambiguity because it tests the raw return against the whole band. Nothing the converted corpus reaches behaves that way, and the only true fix is an instruction-level bottom the managed model cannot express.

**The pointer half needed nothing.** These wrappers pass addresses as `uintptr` — `Getrlimit` emits `RawSyscall(SYS_GETRLIMIT, (uintptr)resource, (uintptr)Ꮡrlim, 0)` — and golib's `ж<T>` → `uintptr` operator does not hand out a token: it calls `EnsureStableAddress()` to pin the managed storage and returns a real address, so the kernel genuinely reads and writes through it. The residual risk is per-struct **layout**, not addressing, and it is the same open class as the Windows non-blittable-wrapper census (see `zsyscall_windows_impl.cs`); `Rlimit` is two `uint64`s, so the first crosser was blittable and worked untouched.

**Portability, stated in the file for the arm64 increment:** `asm_linux_arm64.s` puts `a1..a6` in `X0..X5` with the number in `X8` and reads `r2` from `X1` — which holds `a2`, not `a3` — so an arm64 flavor must echo `a2` and must re-run the `r2` probe there rather than inherit this answer. The variadic shortcut is likewise a per-platform judgment (standard AAPCS64 passes variadic integer args in the same registers as named ones; Apple's arm64 ABI deliberately does not), and a musl target would likely need a `NativeLibrary.SetDllImportResolver` fallback.

The binding itself is `[LibraryImport]` rather than `[DllImport]` — the corpus-wide FFI convention, adopted for the whole surface at once; see [Every P/Invoke is source-generated](#every-pinvoke-is-source-generated) for what that buys and what it cost to reach.

### Every P/Invoke is source-generated

Every native binding in the converted corpus — all fifteen, across five hand-owned files and two operating systems — is `[LibraryImport]`. There is no `[DllImport]` left, and a new one is a mistake rather than a style choice.

The reason is one property of the two attributes and nothing else. `[DllImport]` answers a signature it cannot marshal by marshalling something *else*: a non-blittable struct becomes a temporary copy, and a kernel that writes through the pointer writes into a temporary the caller never reads. That is a wrong **answer**, produced silently, at run time. The source generator refuses to emit the call at all, so the same mistake is a compile error with a line number.

That distinction is not hypothetical here. It is the exact defect `zsyscall_windows_impl.cs` exists to repair — three times over, at 172, 592 and 568 bytes — where the converted `Timezoneinformation`, `win32finddata1` and `ProcessEntry32` hold their inline `WCHAR[]` buffers as managed `array<uint16>` references and the kernel wrote native records over smaller managed objects. The remedy each time was an explicitly blittable mirror plus a pointer, and until now that discipline was enforced only by a reviewer noticing. It is now enforced by the compiler, which is the whole return on the migration: the residual risk of routing Go's kernel boundary through managed structs is per-struct **layout**, and this makes layout a build-time question.

**What the property costs, and the mechanism that pays it.** `SYSLIB1062` requires `<AllowUnsafeBlocks>true</AllowUnsafeBlocks>` unconditionally — even for an all-`nint` signature — because the generated stub is written in terms of pointers. That property is converter-generated from `usesUnsafeCode`, an *emission* fact that observes only C# the converter wrote, so a hand-owned file was structurally unable to ask for it: setting it by hand is undone by the next reconvert overlay. [`[module: go.GoRequiresUnsafe]`](#a-hand-owned-file-can-declare-that-it-needs-unsafe) is that mechanism, and `time` is the package where it is genuinely load-bearing rather than merely honest — its converted emission contains nothing unsafe at all.

**The rejection census.** Twelve of the fifteen declarations the generator accepted **unchanged**, which is itself the finding: `exec_windows.cs`'s five and `zsyscall_windows_impl.cs`'s five were already all-pointer, all-blittable, because both files were written under exactly the discipline the generator checks. Three needed a different signature, and each rejection was a latent hazard rather than a formality:

| Declaration | Rejected for | Became |
|---|---|---|
| `LoadLibraryExW` (`dll_windows.cs`) | `CharSet` has no `[LibraryImport]` equivalent | `StringMarshalling = StringMarshalling.Utf16` — and *not* merely equivalent: UTF-16 marshalling of a `string` is a **pin**, so the stub hands Windows the string's own storage instead of a copy |
| `GetProcAddress` (`dll_windows.cs`) | `CharSet.Ansi` + `BestFitMapping=false` + `ThrowOnUnmappableChar=true`; the latter two are unsupported outright | `byte*`. The mechanical translation (`StringMarshalling.Custom` over `AnsiStringMarshaller`) would have kept the transcode while silently dropping the guard that made it safe — an unmappable rune stops throwing and becomes `'?'`, i.e. a lookup of a *different symbol*. There was never a transcode to preserve: Go passes this entry point a `*byte` with no codepage step anywhere, and the caller already holds that NUL-terminated buffer. The old form decoded it through the ANSI codepage and the marshaller re-encoded it back, lossy in both directions |
| `SetWaitableTimer` (`time_impl.cs`) | `SafeWaitHandle`, and `bool` in both directions | The handle parameter becomes `nint` with `DangerousAddRef`/`DangerousRelease` taken **visibly** in the caller — SafeHandle marshalling is a runtime service, and `[DllImport]` supplied the reference count invisibly, so nothing in the source said where it was taken. `bool` becomes `[MarshalAs(UnmanagedType.Bool)]` on the return and the parameter: its native width is a marshalling decision (Win32 `BOOL` is four bytes, C++ `bool` is one), the runtime marshaller's silent default happened to be right, and the generator refuses to guess. `UnmanagedType.Bool` *is* the four-byte `BOOL`, so the ABI is unchanged and the choice is now written down |

**One converter-side change was required, and it is the interesting one.** `go2cs-gen`'s `PartialStubGenerator` fills every bodyless `partial` method with a throwing stub, which is how the converter emits Go's asm/cgo functions. A `[LibraryImport]` declaration is also a bodyless `partial` method — and **source generators cannot observe each other's output**, so `PartialImplementationPart` is null from there and the declaration looks exactly like an unimplemented asm function. Stubbing it produced two implementing parts and failed the whole package with `CS0757`, for all twelve P/Invokes at once, and only once a hand-own adopted the form. The generator now skips any partial declaration carrying an attribute that obliges a *different* generator to implement it; the test is on the attribute, because the attribute is the obligation. A second such attribute (`JSImport`, `GeneratedComInterface`, …) is added to that set rather than worked around at the call site.

Guarded by the `LibraryImportPartial` behavioral test, which is deliberately a *compile* assertion — both failure modes (`SYSLIB1062` from a missing declaration, `CS0757` from an over-eager stub) are compile failures — carrying a hand-owned `[LibraryImport]` of exactly the corpus's shape, plus a module initializer that calls it, so a program that prints at all is one whose generated marshalling stub reached the kernel.

### The scheduler brackets are a faithful no-op, not an omission

`syscall_linux.go` pulls the pair that wraps every non-`Raw` kernel call:

```go
//go:linkname runtime_entersyscall runtime.entersyscall
func runtime_entersyscall()
//go:linkname runtime_exitsyscall runtime.exitsyscall
func runtime_exitsyscall()
```

Forwarding is the shape this converter reaches for first (see the PULL section above), and it is unavailable here — not marginally: runtime's `entersyscall` opens with `getcallerfp()` / `getcallerpc()` / `getcallersp()`, raw-metal frame intrinsics with no managed realization, and hands them to `reentersyscall`, which drives the P state machine across `sched`, `mp.oldp` and `casgstatus`. None of that state exists in the managed model, so a forwarder faults on the first intrinsic.

`core/syscall/linux/syscall_linux_impl.cs` implements both as empty. That is a *realization*, not a stub: the pair's whole job is to release the P around a blocking call so other goroutines can run on another M, and to reacquire one after. Both are `func()`, they compute nothing any caller consumes, and no converted code reads state they would set — while the obligation they discharge is discharged by the host instead, since a converted goroutine is a .NET thread, a blocking syscall blocks that thread as the CLR expects, and thread-pool injection keeps other work running. This is the same judgment `syscall_impl.cs` records for `runtimeSetenv`/`runtimeUnsetenv`, and it is *not* the fabricated-answer failure mode the project rules against — there is no answer to fabricate. The package's other bodyless declarations (`rawSyscallNoError`, `rawVforkSyscall`, `runtime_doAllThreadsSyscall`, `cgocaller`) are separate questions and stay announcing stubs.

The file lives in `linux/` rather than flat because the declarations it implements are linux-only; a flat implementing part would have no defining declaration on Windows. See *Hand-owns have a platform*.

### `runtime.argslice` — forwarding and populating are ONE change

`os.init()` on unix assigns `Args = runtime_args()`, a bare-shape PUSH whose pushed body is ordinary converted Go: `append([]string{}, argslice...)`. Adding the registry row alone would have *worked* and been wrong — `runtime.argslice` is filled by `goargs()` reading the argv vector off the initial stack, a raw address the CLR does not hand out, so `os.Args` would have come back **empty**: not an error, just a plausible-looking wrong answer.

`core/runtime/goargs_impl.cs` is the sibling of `goenvs_impl.cs` and closes that: a `[ModuleInitializer]` — the faithful stand-in for schedinit's slot, running once before any converted Go code in the assembly — fills `argslice` from `Environment.GetCommandLineArgs()`, which is the managed mirror of the same vector (measured under `dotnet prog.dll alpha beta`: `{".../prog.dll", "alpha", "beta"}` — the program followed by its arguments, exactly Go's shape; *not* `Environment.ProcessPath`, which names the host, and *not* Main's `args`, which omits element zero). The row and the companion therefore land together, or the pair announces a falsehood.

**Windows is untouched in both halves**, and by Go's own construction rather than by an exception: `goargs()` itself opens `if GOOS == "windows" { return }`, and the companion keeps that guard verbatim, so `argslice` stays unset exactly as in Go — which is what `runtime_boring.cs`'s `boring_runtime_arg0` already documents and depends on ("On Windows, argslice is not set").

### `runtime.sysDirectory` — the same pairing, one consumer shape further out

`internal/syscall/windows.GetSystemDirectory` is a PUSH from `runtime/os_windows.go`, and it is the HANDLE consumer shape rather than `argslice`'s bare one: `security_windows.go` carries its own one-arg `//go:linkname GetSystemDirectory` above the bodyless declaration, so the registry row records `bareDecl: false`. Everything else is the `argslice` lesson repeated — which is the point, since it shows the rule is about the STATE behind the push, not about either syntax.

The pushed body is one line (`unsafe.String(&sysDirectory[0], sysDirectoryLen)`), and the buffer behind it is filled by `initSysDirectory()` calling `stdcall2(_GetSystemDirectoryA, …)` from `osinit`. **Neither half runs in the managed model** — `osinit` is the runtime bootstrap the converter emits already marked not-run, and `stdcall` bottoms out in `asmstdcall`, a [`PartialStubGenerator`](#source-generators) throw — so the buffer stays all-zero and its length `0`. A forwarder alone would have returned `""`, turning `net`'s `hostsFilePath = windows.GetSystemDirectory() + "/Drivers/etc/hosts"` into `"/Drivers/etc/hosts"`.

`core/runtime/windows/os_windows_impl.cs` closes it the way `goargs_impl.cs` does: a `[ModuleInitializer]` fills the buffer from `Environment.GetFolderPath(SpecialFolder.System)`. Two details are reproduced rather than tidied. Go appends a separator (`sysDirectory[l] = '\\'; sysDirectoryLen = l + 1`), so the answer really does end in a backslash and `net`'s concatenation really does produce `C:\Windows\System32\/Drivers/etc/hosts`; and Go's `throw("Unable to determine system directory")` is mirrored rather than softened into a short answer that would read as real.

What the missing row cost is out of all proportion to one symbol, and worth recording as a shape to look for: the throw came out of a package-level VAR INITIALIZER, so it surfaced from `net_package`'s type initializer — **every `httptest` consumer died in `net`'s cctor**, whatever it was actually testing.

### Long-path awareness is process SETUP, and golib does what Go's `osinit` does

`runtime.osinit` is not only where `goenvs`/`goargs`/`initSysDirectory` run; it is also where every Go Windows binary opts its own process into long-path handling. `initLongPathSupport()` checks for Windows 10.0.15063 or later and then sets the undocumented `IsLongPathAwareProcess` bit in the PEB's bit field. ntdll's path canonicalizer consults that bit, so with it set a plain, un-prefixed path longer than `MAX_PATH` reaches the kernel intact.

A converted program is an ordinary .NET process and gets none of that. The divergence is measured, not theoretical: at a 434-character path, `Directory.SetCurrentDirectory` fails `ERROR_FILENAME_EXCED_RANGE` (206, `0x800700CE`) where Go's `os.Chdir` succeeds. `MkdirAll` works on both sides because `os.fixLongPath` prefixes `\\?\` explicitly; `Chdir` hands the plain path to `SetCurrentDirectoryW`, and `\\?\` is no escape hatch there — `SetCurrentDirectory` rejects the extended form outright.

`golib/builtin.WindowsLongPaths.cs` therefore sets the same bit from `InitializeGoLib`, golib's analogue of `osinit`, under the same version guard, and defensively: it is a parity measure rather than a prerequisite, since the `\\?\` fallback still works with the flag clear.

**Why not an `<ApplicationManifest>` carrying `longPathAware`.** It reaches the same PEB flag and was the first remedy proposed, but Windows honors a manifest's declaration only when the machine-wide policy `HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled` is *also* 1. Go asks for neither the manifest nor the policy — so a manifested converted binary would still diverge from the Go binary on a default install, where that value is 0, and the manifest measures as a fix only on machines where the policy happens to be on. Doing what Go does is also the smaller change: no per-project manifest artifact and nothing in the emitted `.csproj`, which is what keeps the behavioral corpus and every banked `<pkg>.tests.csproj` byte-identical (CNR compares the emitted `.csproj`).

**What is deliberately left alone.** `initLongPathSupport` also sets `internal/syscall/windows.CanUseLongPaths`, which makes `os.fixLongPath` stop adding the prefix. That flag lives in a converted package golib cannot reference — golib is the root of the dependency graph — and `false` is the conservative side: the extended-prefix spelling still reaches the kernel with the PEB bit set, so the only difference is which spelling it sees.

Guarded by `syscall`'s own `TestGetwd_DoesNotPanicWhenPathIsLong`, which skipped on `Chdir failed: … The filename or extension is too long` until this landed, and passes on both sides now.
### `internal/weak.Pointer` — the CLR already has weak references, so the runtime handle becomes one

`internal/weak` is `unique`'s liveness model, one layer below `internal/concurrent.HashTrieMap` and in
front of the same consumers. The package's entire body is two `//go:linkname` declarations, and both
pushed bodies live in `runtime/mheap.go`:

```go
func Make[T any](ptr *T) Pointer[T] {
	ptr = abi.Escape(ptr)                                   // force the pointee onto the heap
	var u unsafe.Pointer
	if ptr != nil {
		u = runtime_registerWeakPointer(unsafe.Pointer(ptr))
	}
	runtime.KeepAlive(ptr)
	return Pointer[T]{u}
}

func (p Pointer[T]) Strong() *T { return (*T)(runtime_makeStrongFromWeak(p.u)) }
```

`registerWeakPointer` → `getOrAddWeakHandle` → `getWeakHandle` → `spanOfHeap` walks `mheap_` span
metadata to find or hang a `specialWeakHandle` off the span, and `makeStrongFromWeak` loads a word out of
that handle and **re-derives an object pointer from the address**. The managed model populates no span
metadata, and *"what object lives at this address?"* is a question the CLR does not answer at all — so the
pair is registered UNHONORABLE in `linknamePushTargets` and each half announces itself by name (see
[*A cross-package `//go:linkname` PUSH resolves per recorded disposition*](#a-cross-package-golinkname-push-resolves-per-recorded-disposition--forwarder-or-announced-panic)).
The announcement is what pointed at this hand-own; this is what it was pointing at.

**The ruling is the `sync.Mutex` / `internal/concurrent.HashTrieMap` precedent, and it fits better here
than anywhere it has been applied before, because the CLR has first-class weak references of its own.**
`src/core/internal/weak/pointer.cs` carries `[module: go.GoManualConversion]` and contains no span walk.
The contract translates clause for clause:

| Go's contract | Managed mechanism |
|:--|:--|
| `Make(ptr)` never fails; `Strong()` yields the ORIGINAL pointer while the referent is reachable, and nil once the collector has identified it unreachable — **before** a finalizer can resurrect it | `WeakReference<ж<T>>` over the `ж<T>` box, **SHORT** (`trackResurrection: false`); Go's handle likewise clears ahead of finalization |
| A weak pointer does not keep its referent alive | Nothing on the `Pointer<T>` → `handle<T>` → referent path is a strong reference |
| Weak handles are **unique and canonical per byte offset into an object**, so weak pointers made from pointers that compare equal compare equal — and pointers to different offsets within one object do not | A `ConditionalWeakTable` keyed on the referent ALLOCATION whose value is a `ConcurrentDictionary` keyed on the GO POINTER. `ж<T>`'s own `Equals`/`GetHashCode` ARE Go's pointer identity, including "two fields of one struct are different addresses" |
| Equality is retained after the referent is reclaimed | `Pointer<T>` holds the handle STRONGLY, so the handle outlives the referent and keeps answering — it just answers nil forever after |
| A weak pointer made after a resurrection is NEWLY UNIQUE | The table entry dies with the referent (that is what a `ConditionalWeakTable` key is), so a later `Make` mints a fresh handle |
| `abi.Escape(ptr)` — force the pointee out of the frame | Nothing to force: a `ж<T>` IS a heap allocation from construction, whatever its pointee's type |
| `runtime.KeepAlive(ptr)` | `GC.KeepAlive` — load-bearing, not decorative: the referent is reachable from `Make`'s frame only through the argument, and every use of it is finished before the return |

**Why the canonical table does not pin what it indexes — the one subtle claim.** A
`ConditionalWeakTable` is an EPHEMERON: its value is kept alive only while the KEY is independently
reachable, and edges *from* the value *to* the key do not count as reachability. The key is
`ж<T>.ReferentObject` — the same lifetime question `runtime.SetFinalizer` already keys on (`mfinal.cs`),
answered the same way: an element ref resolves to its backing storage, a field ref to the containing
allocation, and a standard heap box to itself. The value holds the `ж<T>` boxes strongly, as dictionary
keys, which is deliberate — for a field or element pointer the box is a per-expression view that would
otherwise die long before the struct does, and `Strong()` must keep returning it; the ephemeron makes
that safe. The handle holds only a `WeakReference`. Composing the three, **a box is reachable exactly
when its referent is**, so one plain `WeakReference` tracks the REFERENT's liveness for every pointer
shape, not merely the standard-box shape.

One shape is deliberately not modelled, and it is Go's error case too: a box that ALIASES A NATIVE
ADDRESS names unmanaged storage the collector does not own, so its managed reachability is not the Go
question. Go answers by faulting (`throw("getWeakHandle on invalid pointer")` — a non-heap address has no
span); here it would observe an eventual nil rather than a fabricated pointer, the safe direction.
Nothing in the converted corpus takes a weak pointer to one.

**A second, independent defect this closes.** `Pointer[T]` is written out rather than left to `[GoType]`,
because the generated struct equality is field-wise `==` **guarded on every type parameter carrying an
`IEqualityOperators` constraint** (`TypeGenerator`'s `hasEqualityOperators` → `AllGenericTypesHaveConstraint`),
and Go's `Pointer[T any]` carries none — so the emitted body was literally
`Equals(other) => false /* missing equality constraints */`. Two weak pointers to one object NEVER
compared equal, contradicting the type's own doc comment and silently defeating `unique.Make`'s
`m.CompareAndDelete(value, wp)`, which could therefore never match and never evict a dead entry.
Equality is the *whole reason* the runtime canonicalizes the handle, so it is hand-written here — and as
`IEquatable<Pointer<T>>`, which the generated form does not implement, so `EqualityComparer<Pointer<T>>.Default`
reaches it without boxing on every lookup. ⚠ **The gate itself is over-conservative and the defect is
corpus-wide**: it disqualifies a struct when ANY type parameter lacks the constraint, even when no
field's type mentions that parameter. `unique.Handle[T]` is the other confirmed victim — its single field
is a `ж<T>`, which defines `==` for every `T`, yet its generated `Equals` is `false` too, so
`unique.Handle` values never compare equal either. That is a generator fix rather than a hand-own, and is
left to its own arc.

**Guarding measurement.** `internal/weak`'s own suite now links and runs
(`go2cs -tests -test-action all "<GOROOT>/src/internal/weak" src/core/internal/weak`): **`TestPointerEquality`
PASSES against `go test`** — the canonicalization clause, the hardest one, validated end to end.
`TestPointer` and `TestPointerFinalizer` do not, and the reason is the roster's already-named
**`codegen-liveness`** class rather than the weak model: both hold the referent in a live C# local (`bt`)
across the `runtime.GC()` that is supposed to kill it, where Go's per-safepoint liveness maps drop it at
its last use. (Neither is *disclosable* — `TestPointerFinalizer` does not fail an assertion, it blocks
forever on `<-done` waiting for a finalizer that a still-rooted object can never queue — so
`internal/weak` does not bank.) A dedicated probe separates the two — a referent
created and dropped inside a `[MethodImpl(NoInlining)]` helper is reported collected, and only a weak
pointer that had `Strong()` called on it *earlier in the same frame* stays alive:

```
PASS  Strong() is nil once the referent is unreachable (never probed)
FAIL  Strong() is nil once the referent is unreachable (probed first)
```

with a self-keyed `ConditionalWeakTable` control and the two-level table control both collecting, so the
ephemeron reasoning above is confirmed rather than assumed. `unique` reads the same way from the other
side: every `TestHandle` subtest that gets far enough reports **only** `v0 != v1` (the `[GoType]` equality
gate above) and never `v0.Value() != v1.Value()` — i.e. both `Make` calls interned the *same* `ж<T>`, which
is exactly what canonical weak handles plus `LoadOrStore` are for.

`pointer.go` is this package's only Go file, so marking it makes `internal/weak` **fully hand-owned**: the
driver `continue`s on `unmarkedFileCount == 0` and stops re-emitting `internal.weak.csproj`,
`package_info.cs` and `README.md`, and no `pointer.cs.auto` review sibling is produced — the position
`internal/godebug` and `internal/concurrent` are already in. The marker census moves **39 → 40**.

### `unique.clone` — a raw-offset string walk the managed model cannot express, hand-owned after the `@string` window made it GC-fatal

Go's `unique.clone[T]` rewrites every string field of a just-interned value in place, addressing each
by raw ABI offset so an interned handle never keeps a large parent string alive:

```go
func clone[T comparable](value T, seq *cloneSeq) T {
	for _, offset := range seq.stringOffsets {
		ps := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&value)) + offset))
		*ps = stringslite.Clone(*ps)
	}
	return value
}
```

The converted form — `(ж<@string>)(uintptr)((uintptr)Ꮡvalue + offset)` followed by a `.Value` write —
adds a **Go ABI** offset to the transient interior address of the movable `ж<T>` heap box, whose CLR
field layout is unrelated to Go's ABI (`EnsureStableAddress` cannot pin a box whose `T` contains
references, and for `[2]struct{a string}` the +16 offset is outside the 8-byte `array<T>` reference
that is the entire CLR value). Every such store landed on the box's OWN fields. While `@string` was a
single 8-byte reference the damage was a type-confused slot holding a valid object — silently wrong
values, nothing the collector trips over. When `@string` became an offset/length **window**
(`fc6d8c179`, r57c — 16 bytes: `byte[]` + two `int`s), the same store's integer tail began landing in
an adjacent GC-scanned reference slot, and the next collection — which `unique`'s own `drainMaps`
forces via `runtime.GC()` — walked a garbage pointer and fail-fasted the process with
`COR_E_EXECUTIONENGINE` (0x80131506). Bisected, and reproduced in ~25 lines against golib alone, by
the 2026-08-12 unique-bisect lane (the board's scout-batch-1 `unique` entry holds the full record).

`src/core/unique/clone.cs` therefore carries `[module: go.GoManualConversion]` — the standard S1
managed-referent remedy — with **only `clone<T>` departing from the conversion**. Its contract is
"makes a copy of value, and MAY update string values found in value with a cloned version": the
cloning is a retention optimization, never a semantic requirement, so the hand-own does the
`T == string` case exactly (a right-sized `stringslite.Clone`, no address arithmetic — worth more,
not less, now that `@string` windows share backing) and returns aggregate values unchanged. The one
observable divergence from Go is retention: an interned aggregate's strings keep sharing their
original backing arrays. Equality, identity and intern-map drainage — what `unique.Make` is *for* —
are unaffected. `makeCloneSeq` and the `cloneSeq` builders remain the verbatim conversion (pure
descriptor arithmetic, still validated by `TestMakeCloneSeq`), so a `clone.cs.auto` review sibling is
emitted on every reconvert as usual.

### `internal/cpu.getGOAMD64level` — a BUILD constant, so the honest answer is the baseline

Go declares `getGOAMD64level() int32` bodyless and implements it in `cpu_x86.s`, where it is not code at
all but a compile-time constant selected by the `GOAMD64_vN` define the toolchain sets from
`go env GOAMD64`:

```asm
TEXT ·getGOAMD64level(SB),NOSPLIT,$0-4
#ifdef GOAMD64_v4
	MOVL $4, ret+0(FP)
#else
#ifdef GOAMD64_v3
	MOVL $3, ret+0(FP)
#else
#ifdef GOAMD64_v2
	MOVL $2, ret+0(FP)
#else
	MOVL $1, ret+0(FP)
#endif
```

The question it answers is *which amd64 microarchitecture level was this BINARY built for*, never *which
level does this CPU support* — the two differ constantly, and Go depends on the difference: `doinit`'s
option table gates the `sse3`/`avx`/`avx512` GODEBUG knobs on `level < 2/3/4`, so a v1 build running on a
v3 machine keeps them switchable. go2cs emits portable C# with no GOAMD64 define, no
microarchitecture-gated emission and no instruction-set floor above the amd64 baseline, so the faithful
answer is the same constant Go's own assembly produces for a build without a `GOAMD64_vN` define: **1**.
That is a measured property of the emission rather than a placeholder value, and probing the host through
`System.Runtime.Intrinsics.X86` would answer a *different question* — the inverse-atomic rule's exact
prohibition, since the returned number would look truthful while meaning something else.

`cpu_x86_impl.cs` returns it under `[module: go.GoManualConversion]`, registered as
`manualConversionFuncs["internal/cpu"]["getGOAMD64level"]`, so the converter leaves the standard
placeholder comment where the bodyless partial was. A/B footprint: **one corpus file**. Demonstrated
consumer: `internal/cpu`'s own `TestDisableSSE3`, whose first statement is
`if GetGOAMD64level() > 1 { t.Skip(…) }` — against the unimplemented `PartialStubGenerator` stub that
guard was an infrastructure-error, and it was the package's only divergence (7 of 8). With the constant in
place the test reads 1, walks on into `runDebugOptionsTest`, and skips exactly where Go does:
`internal/cpu` validates **8 of 8**.

### `StructField.Tag` is a REAL read — the converter has always emitted the tag, nothing had ever read it

The converter emits a tagged field's Go struct tag verbatim at the declaration:

```go
NamedCurveOID asn1.ObjectIdentifier `asn1:"optional,explicit,tag:0"`
```
```csharp
[GoTag(@"asn1:""optional,explicit,tag:0""")]
public asn1.ObjectIdentifier NamedCurveOID;
```

`GoTagAttribute` aliases `System.ComponentModel.DescriptionAttribute`, so the text survives into
metadata. It has done so since tags were first emitted — and until 2026-08-09 **nothing in the
corpus read it**: golib's Go-field projection (`GoReflect.GoFieldInfo`) carried no tag, and the
reflection bridge's `rtype.Field` left `StructField.Tag` at its zero value. Every converted
struct therefore reported as UNTAGGED, and every tag-driven decoder in the standard library —
`encoding/json`, `encoding/xml`, `encoding/asn1` — saw a type with no tags at all.

The failure that surfaced it is subtle rather than loud, which is the point: `encoding/asn1`
omits an `optional` field whose value `DeepEqual`s its zero, so with the tag invisible
`crypto/x509`'s `marshalECPrivateKeyWithOID(k, nil)` MARSHALLED the nil `NamedCurveOID` instead
of omitting it, and `makeObjectIdentifier` rejected the empty arc list — `asn1: structure
error: invalid object identifier`, the whole of `crypto/ecdsa`'s `TestEqual`. Nothing about the
message points at reflection.

`GoFieldInfo` gains `Tag`, read from the declaration's attribute (the promoted-embed arm reads
it off the backing box field, since Go allows a tag on an embedded field); `rtype.Field`
surfaces it as `StructTag`. `Offset`, `PkgPath` and `Anonymous` stay **unpopulated**: no
truthful read backs them here, and a descriptor field whose read cannot be honored must not be
made to look truthful. (Guarded by the `ReflectStructTagCopy` behavioral test — raw tag text,
`Tag.Get` for two keys, `Tag.Lookup`'s absent-vs-empty distinction, and untagged fields
answering `""`, output-compared vs Go.)

### `reflect.Copy` is bridged element-wise — the auto form is a flat two-header memory move

`reflect.Copy` reinterprets BOTH operands' data words as `unsafeheader.Slice` headers
(`*(*unsafeheader.Slice)(dst.ptr)`) and hands them to `typedslicecopy`. That is a raw memory
move with no managed form, and on the bridge's never-populated `ptr` slot it dereferences a nil
`ж` outright. `encoding/asn1`'s `parseField` copies every parsed `[]byte` into its destination
through it, so this was `crypto/x509`'s `ParsePKCS8PrivateKey` and therefore the second half of
`crypto/ecdsa`'s `TestEqual` — reached only once the tag fix above let the marshal succeed.

The bridge copies element-wise through the same golib container interfaces every other bridged
container method uses, which keeps the ALIASING exact rather than approximating it: a slice
VALUE windows the backing store it shares with its parent, so an indexer write is a write the
parent sees — precisely what `typedslicecopy` does to the same memory. Kind and element-type
validation mirror Go's, including the documented special case where `src` may be a `String`
when `dst`'s element type is `byte`; a nil container on either side copies nothing, matching
Go's zero-length header. (Guarded by the `ReflectStructTagCopy` behavioral test — slice←slice
truncating at either side, slice←string, an addressable array through `Elem()`, a nil
destination, and a window slice whose copy must be visible in the ORIGINAL backing array.)

### `reflect.Type.Name()` — a DEFINED type HAS a name even when its underlying type is a composite

Go's rule is about DEFINEDNESS, not shape: `Name()` reports the type's name within its package for
any defined type and `""` only for a type that was never defined — `[]int`, `map[string]int`, `*T`,
`chan int`, `interface {}`, `struct { … }`. `type testSET []int` is defined, so its `Name()` is
`"testSET"` even though its underlying type is a slice.

The bridge had that backwards. Go's own `rtype.Name()` gates on the descriptor's `TFlagNamed` bit
(`abi.Type.HasName()`), which a **synthesized** `abi.Type` never carries, so the hand-owned
`rtype.Name()` substituted a shape test — `GoReflect.ElementType(st) is not null`, i.e. "does this
type have an element type?". That is true of a defined container exactly as it is of an unnamed one,
so every `type S []T` / `[N]T` / `map[K]V` / `chan T` / `*T` in the corpus reported no name at all.

The tell was already in the same descriptor: `PkgPath()` reads the SAME managed nesting and answered
`"main"` for those types while `Name()` answered `""` — a pair Go's own model cannot produce, since
a type with a package path is by definition a defined type.

The visible symptom was one byte. `encoding/asn1`'s `getUniversalType` distinguishes a SET from a
SEQUENCE on the type's name and nothing else:

```go
if strings.HasSuffix(t.Name(), "SET") {
    return false, TagSet, true, true
}
return false, TagSequence, true, true
```

so `Marshal(testSET([]int{10}))` produced `300302010a` where Go writes `310302010a` — `0x30`
SEQUENCE for `0x31` SET, with no error, no panic and no other divergence anywhere in the encoding.

The gate is now `GoReflect.HasGoName`, the managed stand-in for `TFlagNamed`. It mirrors
`GoTypeName` ARM FOR ARM, because `Name()` IS that method's output with the package qualifier
trimmed — the two disagreeing would let a type report a name it does not have, or hide one it does.
False for exactly the arms that render Go structurally: the raw golib containers matched by open
generic definition (`slice<>`/`array<>`/`map<,>`/`channel<>`/`ж<>`), `object` (`interface {}`),
`EmptyStruct` (`struct {}`), an anonymous-struct lift (`[GoType("dyn")]` without a `[GoLocalName]`,
which would make it a named function-local type), and the pointer-sourced adapter that stands for
`*T`. True everywhere else — including the predeclared scalars, since Go's `int` IS a named type.

The distinction the fix turns on is that a DEFINED container is not a golib container: the converter
emits it as its own wrapper type that merely IMPLEMENTS the container interface, which is why the
open-generic-definition test separates the two where an element-type probe cannot:

```go
type intSET []int
type byteArray [4]byte
type stringMap map[string]int
type intChan chan int
type intPtr *int
```
```csharp
[GoType("[]nint")] partial struct intSET;
[GoType("[4]byte")] partial struct byteArray;
[GoType("map[@string, nint]")] partial struct stringMap;
[GoType("chan nint")] partial struct intChan;
[GoType("ж<nint>")] partial class intPtr;
```

Three further answers change with it, all in the same direction and none of them a value Go can
produce: `interface {}`, `struct {}` and a lifted anonymous struct used to return their STRUCTURAL
spelling from `Name()` (there is no dot to trim, so the whole string came back) and now correctly
return `""`. `String()` was never affected — it has no such gate and rendered all of these correctly
throughout, which is why the defect stayed invisible to `%T` and to the `ReflectliteTypeName` guard.

(Guarded by the `ReflectStructTagCopy` behavioral test, which pairs each of the five named shapes
with its unnamed control and re-runs asn1's own `HasSuffix(Name(), "SET")` decision. Measured on
`encoding/asn1`'s converted suite: **37 of 38**, up from 35 — it closes `TestMarshal` #37 and also
`TestCertificate`, whose "sequence tag mismatch" and empty RDN name had been left unattributed on
the board and are the same root, since its `RDNSequence` is a `[]RelativeDistinguishedNameSET`.)

**Still open, and dormant:** `abi.Type.HasName()` itself remains `false` for every synthesized
descriptor. `internal/reflectlite.rtype.Name()` is the ordinary converted Go body and gates on it,
so it answers `""` for EVERY type — strictly worse than what `reflect` had. Nothing in the corpus
calls it (reflectlite's consumers, `context` and `errors`, use only `String`/`Kind`/`Comparable`/
`AssignableTo`/`Implements`), so it is recorded rather than fixed. Populating the bit would ALSO
change `directlyAssignable`'s `T.HasName() && V.HasName()` short-circuit — which is currently
over-permissive in both packages — and that is a corpus-wide assignability change, not a naming one.

### `abi.Type`'s SPECIALIZATIONS are synthesized, not downcast — `StructType()` / `ArrayType()`

Go's `(*structType)(unsafe.Pointer(t))` is the **prefix-downcast** idiom: the linker really allocated a
`structType` and handed out a pointer to its embedded `Type` header, so casting back reaches the
sub-record. The section on `Reinterpret` above names this as the one case the managed arm deliberately
does not cover — nothing sits behind a `ж<abi.Type>` but an `abi.Type` — and these are the two sites
where converted code took that cast anyway.

The failure is not the contained wrong read the address route usually gives. `Reinterpret` correctly
**refuses** to alias managed storage for a reference-bearing pair (aliasing would fabricate object
references), so it fell through to the raw address and read `ΔStructType`'s fields out of the memory
that follows the descriptor's value slot. Probed on `abi.TypeFor[testStringStruct]()`:

```
Fields.Length   8830452760576     <- an address fragment read as a slice length
Fields.Capacity 16                <- the descriptor's OWN Size_, bleeding through the shifted view
```

`m_array` happened to land on a real heap object, so indexing it threw `IndexOutOfRangeException`
rather than access-violating — a caught CLR type-safety break. That is **six of `unique`'s nineteen
rows**, thrown on the first iteration of `unique.buildStructCloneSeq`, and `internal/reflectlite`'s
`NumField`/`Len` read the same garbage.

Both specializations are therefore hand-owned in `internal/abi/type_impl.cs` (registered in
`manualConversionFuncs` as `Type.StructType` / `Type.ArrayType`, so the converter emits a placeholder
comment for the Go bodies) and **synthesized from the descriptor's carried `System.Type`**, exactly as
the descriptor itself is:

| Field | Answer |
|---|---|
| `StructType.Fields[i].Typ` | `synthType` of the projected Go field type, dims-stamped from the declaring zero instance |
| `StructType.Fields[i].Offset` | the field's **Go** (amd64) byte offset |
| `ArrayType.Len` / `.Elem` / `.Slice` | the descriptor's carried array dims, the element descriptor, and `[]T`'s |

The offsets are Go's numbers, not the CLR's — a Go `string` is 16 bytes where `@string` is an 8-byte
reference — and they come from the SAME walk that stamps a descriptor's `Size_`
(`GoReflect.GoFieldOffsets`, factored out of `GoSizeOf`'s struct arm), so a field's `Offset` and its
struct's `Size_` cannot disagree. `unique`'s `cloneSeq` values are the demonstrated consumer:
`struct{ z float64; b string }` → offsets `[8]`, `[2]struct{ a string }` → `[0 16]`, `[3]string` →
`[0 16 32]`, each matching `go test` exactly.

Two things are deliberately **not** invented, following the r39d rule that a descriptor field whose
read cannot be honored must not be populated to look truthful. A descriptor with no `System.Type`, or
a struct holding a field whose Go size is unknowable (one unknown size makes every later offset a
guess), answers Go's **nil** — which every Go caller already tests. And `StructField.Name` /
`StructType.PkgPath` stay the zero `ΔName`: a `ΔName` is a pointer into the linker's name blob and
every reader of one walks it with `addChecked` raw-address arithmetic, the same route that produced
the garbage above, whereas Go's own `ΔName.Name()` answers `""` for a nil `Bytes` — so the zero value
is a state the format *defines* rather than a fabrication. A named field descriptor already comes
from `reflect`'s hand-owned `rtype.Field` over `GoReflect.GoFields`, and no converted caller of
`abi.StructType` reads a field name.

Same defect class, **still open** and deliberately not chased here: `Type.Elem()`, `MapType()`,
`FuncType()`, `InterfaceType()`, `Key()` and the free `Len()` reinterpret the same way. `Elem()`'s
pointer arm is the mechanism behind the board's separate "`abi.TypeFor<T>()` is silently wrong for an
interface `T`" root.

Guarded by `GolibTests.GoStructLayoutTests` (Go offsets and sizes for the exact shapes `unique`'s
`TestMakeCloneSeq` exercises, plus alignment padding — removing the per-field alignment rounding fails
`FieldOffsets_ApplyGoAlignmentPadding`), and measured by `unique`'s own suite: **1 → 4 of 19 matched**,
with all six `IndexOutOfRangeException` rows gone and the three `TestHandle` ones moved on to the
`internal/weak` linkname root behind them.

### Realizing the runtime TIMER contract (`Sleep` / `newTimer` / `stopTimer` / `resetTimer`)

`time`'s four timer entry points have no Go body — they are `//go:linkname`'d into `runtime/time.go` — so the converter emits them as bodyless `partial`s that the [`PartialStubGenerator`](#source-generators) fills with `NotImplementedException`. A stub throw on a timer path is uniquely destructive: it lands on whichever goroutine touched the timer, and an unrecovered panic in *any* goroutine terminates the process, so every package that so much as called `time.Sleep` once was unreachable. `time_impl.cs` supplies the four bodies (the same supplemental-companion mechanism as `math_impl.cs` and the clock reads above it), and the whole model is a single `_impl.cs` region — no converter or golib change.

**Service model: one heap, one thread — Go's own pre-per-P design.** Go keeps timers in per-P heaps run by whichever P first notices a deadline; the managed model is one deadline-ordered heap serviced by one dedicated background thread, the shape Go itself used before per-P timers (the old runtime `timerproc`). One thread is sufficient *and* ordering-faithful for a specific reason: the only two callbacks package `time` ever installs are `sendTime` (a **non-blocking** channel send) and `goFunc` (which only starts a goroutine), so no timer callback can occupy the service thread and delay a later deadline. Callbacks are collected under the lock and invoked after releasing it, in deadline order.

**Precision: the same OS object Go uses.** `System.Threading.Timer` is *not* the mechanism, because its resolution is the Windows timer tick — measured at ~15 ms on the development host, which would make a 1 ms Go timer fire 15× late and let two timers less than a tick apart fire **out of order**. Both `Sleep` and the service thread instead wait on a Windows high-resolution waitable timer (`CreateWaitableTimerExW` + `CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`), which is precisely what the Go runtime creates for its own sub-tick sleeps (`runtime/os_windows.go` `createHighResTimer`/`usleep`), cached **per thread** exactly as Go caches it per M. The wait is driven off the same monotonic source `runtimeNano()` reads, so timer deadlines stay coherent with `Now()`/`Since`/`Sub`; a truncation in the 100 ns due-time unit can only wake early, and the deadline loop re-waits the remainder, preserving Go's "at least `d`" guarantee. Where no high-resolution timer exists the fallback waits coarsely to within a millisecond and spins the remainder — correct, but tick-quantized, which is the whole reason the high-resolution path is preferred. The interop is `[LibraryImport]` over `nint` and `ref long`, and this file is where the migration to it cost the most: the source generator marshals no `SafeHandle` and guesses at no `bool`, so the handle's reference count is now taken visibly in `Arm` and both `bool`s carry an explicit `UnmanagedType.Bool`. It also needs `/unsafe`, which `time`'s converted emission does not, so `time_impl.cs` carries a [`[module: go.GoRequiresUnsafe]`](#a-hand-owned-file-can-declare-that-it-needs-unsafe) declaration — the mechanism that exists because the csproj is regenerated on every transpile. See [Every P/Invoke is source-generated](#every-pinvoke-is-source-generated).

**Hidden state keyed by BOX IDENTITY.** Go's `runtime.timeTimer` carries the timer state in fields *after* the two `time` can see — sleep.go's "extra fields after the channel, reserved for the runtime and inaccessible to users". The managed equivalent hangs a `runtimeTimer` record (`when`/`period`/`f`/`arg`/`gen`) off the `ж<Timer>` box's **reference identity** through a `ConditionalWeakTable`: `stopTimer`/`resetTimer` are always handed the very box `newTimer` returned. Weak-keyed, so an unreferenced `Timer` stays collectible (Go 1.23 recovers unreferenced timers) — while an *armed* timer's state is independently kept alive by the service heap, which is what makes a bare `time.After(d)`, whose `Timer` is dropped on the spot and only the channel kept, still fire.

**The Stop/Reset contract and the fire race.** Both report `when > 0` — Go's `timer.stop`/`timer.modify` compute `pending` exactly that way — so *pending* means armed and not yet fired, and `when` doubles as the arm marker (0 = stopped, or a one-shot that fired). Every change to `when` bumps a generation counter, and heap entries carry the generation they were queued with; a `Stop` or `Reset` therefore cancels an already-queued firing **without** removing it from the heap (Go leaves the stale entry too, marked `timerModified`/`timerZombie`). One lock guards the heap *and* every timer field — Go's finer-grained per-timer + per-P scheme exists to scale across Ps, and with a single heap there is one lock and hence no lock-ordering hazard — so a `Stop`/`Reset` racing the firing callback can neither double-fire nor lose a re-arm: either the service thread already took the callback (and `when` is 0, so `pending` correctly reports false), or the generation bump invalidates its queued entry and the callback never runs. A `Ticker` re-arms by **whole periods** past a late firing (`next = when + period*(1 + delay/period)`), which keeps the tick *phase* aligned to the original schedule instead of drifting; combined with `sendTime`'s non-blocking send onto the cap-1 channel, a receiver too slow to keep up therefore **loses** ticks rather than seeing them queue — Go's documented "adjust the time interval or drop ticks to make up for slow receivers".

**ONE firing per timer per pass — the service pass reads the clock exactly once.** `serviceTimers` samples `now` once and threads that single value through the whole drain. That is the invariant, not an optimization, and Go does the same for the same reason: the scheduler samples the clock in `timers.check` and hands it down through `timers.run(now)` to `timer.unlockAndRun(now)`, never re-reading it inside a pass. The consequence is a theorem rather than a heuristic — *within one service pass every timer fires at most once*. A one-shot leaves the heap with `when` cleared; a periodic timer is re-armed to `next = when + period*(1 + delay/period)` with `delay = now - when >= 0`, and writing `delay = q*period + r` for `0 <= r < period` gives `next = when + period*(1 + q) = now + (period - r)`, which `r < period` makes **strictly greater than `now`** — so the re-peek always takes the "not yet due" branch and the drain ends. It holds for every period, down to 1 ns. Re-reading the clock per iteration broke the theorem and was a real defect (recorded r36, fixed r39): the advanced `when` lands one nanosecond ahead, a freshly read `now` has already passed it, and the same ticker fires again — for as long as consecutive reads of the ~100 ns monotonic source keep advancing. The burst is invisible while nobody is receiving (the non-blocking send onto a cap-1 channel drops all but one) but `time`'s own `TestChan` *is* receiving: the two stale values an async ticker is allowed became three or more, and `noTick` reported "extra tick" in **all three** `asynctimerchan` modes — which the then-standing asynchronous-timer-channel divergence (now implemented, below), scoped to the sync mode, never explained. The invariant does not rate-limit: a pass fires each due timer once and then waits until the new head deadline, which for a fast ticker is already past, so the wait returns at once and the next pass fires it again — exactly as Go's scheduler calls `check` again with a fresh `now`. The bound is on re-firing *within* a pass, which is what "drop ticks to make up for slow receivers" means. Nor can it delay anything: `next` depends on `now` only through the non-decreasing floor `delay/period`, so hoisting the read can only make the pass's `deadline` smaller or equal, and `waitUntil` recomputes `remaining` from a fresh clock — no timer can wake later than the per-iteration version would have woken it, and when `delay < period` (the common case) the deadline is identical either way. A timer coming due *during* a pass waits for the next one and that is not a delay either, because the head of the heap is the minimum `when` among live entries, so the deadline is already past by the end of the drain and the next pass starts at once. Two adjacent places are deliberately **not** Go, and the fidelity claim should not be read past them: Go's `check` releases the timer-set lock around *each* callback and re-validates the head between them, so a `Stop` landing mid-pass cancels the callbacks after it, where this drain commits the whole batch under one lock hold and then runs it; and Go keeps one heap entry per *timer* (repositioned in place, zombies swept) where this keeps one per *arm*, reclaiming a dead entry only when it reaches the head. Both predate the single clock sample and are narrowed by it, since a frozen `now` commits a smaller batch. A standing constraint follows from the same arithmetic: at the instant a ticker is stopped or reset at most **two** of its ticks can exist — one buffered, one committed but not yet sent. In *synchronous* mode both are now revoked outright (the drain takes the first, the `seq` check the second — see the next entry), so the guarantee no longer rests on that count; in `asynctimerchan={1,2}` it still does, and two is exactly what `drainAsync` drains, so the margin there is zero and any later change that lets two ticks for one timer be committed before their callbacks run re-breaks `noTick` in those modes without touching `time_impl.cs`.

**`tick.cs` is hand-owned, and revertibly so.** Go builds a `Ticker` by reinterpreting the `*Timer` the runtime returned — `(*Ticker)(unsafe.Pointer(newTimer(…)))`, plus the mirror-image casts in `Ticker.Stop`/`Reset` — because "Ticker and Timer have the same layout". Converted literally those three reinterprets **compile but cannot work**: each is a managed-box `uintptr` round-trip whose address escapes its `fixed` pin, and nothing references the `ж<Timer>` `newTimer` produced, so the ticker's storage is collected at the next GC (the retained-pointer worst case of the corpus-wide hazard in `docs/phase4/FINDING-managed-box-uintptr-lifetime.md`). Pinning is not available either — `Ticker` holds a `channel<Time>`, a managed reference, and `GCHandle.Alloc(…, Pinned)` rejects a type containing references — so the raw-address model has no sound form here. The file (marked `[module: go.GoManualConversion]`, whole-file) builds the `Ticker` directly and addresses its timer by box identity, which is the standing managed-referent ruling at the top of this section; every doc comment and the struct declaration are the converted output verbatim, and the file should be **deleted** in favor of its `tick.cs.auto` sibling the moment a general managed-reinterpret capability lands.

**Synchronous timer channels — the property, and the two mechanisms that keep it (#37196).** Go 1.23 made a chan-based `Timer`/`Ticker` channel *synchronous*, and the name describes a guarantee rather than a plumbing change: the channel is still created as `make(chan Time, 1)` — a tick can be produced with nobody receiving — and what changed is that its owner may take a tick **back**. The whole proposal is one sentence: *a `Stop` or `Reset` prevents any tick generated before the call from being received after it.* Equivalently, every value a receive on `t.C` observes was committed **after** the most recent `Stop`/`Reset`, so there are no stale values and the pre-1.23 "drain `t.C` when `Stop` returned false" idiom is unnecessary. Two consequences are directly observable: `Stop`/`Reset` report `pending == true` when they **revoke** an unreceived tick, not only when the timer was still armed; and `len(t.C)`/`cap(t.C)` are **0**, because a buffered value the next `Stop` may confiscate must not be advertised.

The guarantee needs three mechanisms — two of Go's, at the same two layers, plus one this model needs because it fires eagerly where Go fires lazily. **(1) Send lock + sequence, in `time_impl.cs`** — Go's `timer.sendLock`/`timer.seq`. A firing is decided under the heap lock but must be delivered with it released, so a `Stop`/`Reset` can land in between; the service pass therefore only *offers* a tick. It captures `seq` when it commits the firing (exactly where Go's `unlockAndRun` copies `t.seq` under `t.mu`), then takes that timer's `sendLock` and re-checks — a mismatch means a `Stop`/`Reset` intervened and the offer is **abandoned** (Go expresses the same thing by replacing `f` with a no-op). `Stop`/`Reset` bump `seq` while holding `sendLock`, so every offer is wholly before them or wholly after; it cannot straddle. `seq` is deliberately *not* the heap generation `gen`: `gen` is bumped by a firing as well, which is what makes it the heap's liveness token, and a delivery check has to survive its own firing. **(2) Buffer drain, in golib** — `channel<T>.DrainBuffer()`, Go's `runtime.timerchandrain`: the `seq` check stops offers not yet sent, and a tick *already in the buffer* is revoked by emptying it. Both `Stop` and `Reset` drain, and either reports `pending` true when it discarded something. **(3) The `offered` flag, with no Go counterpart.** Mechanisms 1 and 2 revoke correctly but cannot between them *answer* correctly: in the very window mechanism 1 exists to cover, the tick is in neither place a `Stop` looks — `when` is already 0 and the buffer is still empty — so `Stop` would revoke the tick and then report that there had been none. Go never reaches that state, because a sync-mode chan timer nobody is receiving from is not in a heap at all (`timer.needsAdd` requires `t.blocked > 0`) and therefore never fires; eager firing is what opens the window, so eager firing is what has to close it. `offered` is set under the heap lock where the firing is committed, cleared under `sendLock` where it resolves, and read by `Stop`/`Reset`, which hold both. Together the three cover every place a tick can be: scheduled, committed-but-unsent, or buffered. A tick handed **directly** to a parked receiver is none of them — but that receive already committed, and it committed while the sender held `sendLock`, hence strictly before the `Stop` waiting on that lock could return; the guarantee is about ticks received *after* the call, so a direct hand-off falls inside it rather than being an exception.

The channel side is a general hook, not a `time` special case: golib gains `IChannelTimer` (Go's `hchan.timer`), a channel installs its owner with `AttachTimer` before the timer is armed, and `channel<T>.Capacity`/`Length` return 0 while the owner answers `HidesBuffer` — asked **live**, because `GODEBUG=asynctimerchan` selects the model at every observation, exactly as `runtime.chanlen`/`chancap` re-read the setting. `IsUnbuffered` deliberately still reports the *physical* shape (a timer channel's send does not rendezvous), since Go exposes only `cap()`.

Two places are deliberately **not** Go. Upstream `Stop` drains after releasing `sendLock` and `Reset` drains before releasing it; this holds `sendLock` across the drain in **both**, because Go can afford the looser order only thanks to lazy heaping (a sync-mode chan timer is in a heap only while a receiver is blocked on it, so nothing can produce a tick between `Stop`'s unlock and its drain) and this model's service thread is always live and always eager — a `Reset` racing another goroutine's `Stop` could otherwise have its fresh tick drained by the `Stop`. And Go fires a sync-mode chan timer **lazily** (`runtime.maybeRunChan`, at receive time) where this fires eagerly from the service heap; that is what mechanism 3 exists for, and *with* it `Stop`'s answer is the same either way — but eager firing still costs Go 1.23's early GC of unreferenced timers, since an armed timer stays reachable from the service heap. `GODEBUG=asynctimerchan` selects the model as upstream: `0` (default) synchronous, `1` pre-1.23 asynchronous (package `time`'s own `syncTimer` withholds the channel, so none is ever registered), `2` asynchronous semantics over a registered channel — and modes 1 and 2 run the pre-existing model unchanged, every branch being gated on the live setting. Measured on `time`'s own `TestChan`: all six subtests (`asynctimerchan={0,1,2}` × Timer/Ticker) pass, where before only `{1,2}` did. Guarded end-to-end by the `SyncTimerChannel` behavioral project, whose stdout is byte-compared against `go run`: `Stop`/`Reset` `pending` after a fire, no stale tick, `len`/`cap` 0, `AfterFunc` untouched, and three things a single-shot test cannot see: 200 `Reset`-to-imminent timers that must still deliver (the counter-property that keeps the drain honest), 600 ticker `Stop`/`Reset`-vs-firing races that must revoke exactly nothing, and two 600-timer batches armed against ONE absolute deadline and stopped/reset at that instant, every one of which must report `pending`. The last is what exposed mechanism 3's absence, and it only samples the window because the batch and the caller's sleep share a deadline — give each timer its own relative duration and the caller wakes milliseconds after the last tick has already landed, so the window is never entered and a broken implementation passes.

**Reach.** Timers gate roughly 35 stdlib suites. `encoding/base64`'s `TestDecoderIssue3577` and four of `internal/singleflight`'s five tests flip to passing on this alone; `singleflight`'s fifth needs 1000 *simultaneously parked* goroutines, which was the separately-documented cooperative-scheduler limitation in golib's `goǃ` (a goroutine was a ThreadPool work item and held its thread while parked, so the 256-thread floor bounded how many could be parked at once), not a timer issue. The dedicated-thread executor retired that bound (`docs/phase4/DESIGN-cooperative-scheduler.md`).

### The runtime's PROCESS-CONTROL surface: implement the CONTRACT, never the mechanism

`runtime`'s public control API — `GC`, `GOMAXPROCS`, `Gosched`, `Stack`, `ReadMemStats`,
`LockOSThread`/`UnlockOSThread` — converts faithfully and **compiles**, then dies on its first call.
Each body drives machinery that has no managed counterpart: `GC()` → `gcStart` → `acquirem` →
`getg()`; `GOMAXPROCS(n)` → `stopTheWorldGC` → `semacquire` → `getg()`; `Gosched()` →
`mcall(gosched_m)`. `getg` and `mcall` are Go **compiler intrinsics** — a register read and a stack
switch — so the [`PartialStubGenerator`](#source-generators) fills them with a throw, and the throw
lands wherever the caller ran. On a goroutine (a managed thread) that is an unhandled exception and
**terminates the process**: sync's suite lost 28 of its 51 tests to one such throw.

The ruling is the one sync's `Mutex`/`notifyList` established: **where a Go mechanism has no managed
counterpart but its public contract does, reimplement the CONTRACT at the API boundary and never
emulate the mechanism.** Synthesizing a plausible `g`/`m` so the converted scheduler can walk it buys
nothing — the code underneath still wants a real run queue, a real heap and real stacks. The seven
declarations above are dropped from emission by the type-level registry
(`manualConversionFuncs["runtime"]`) and answered in a hand-owned `runtime/managed_impl.cs`;
everything below them stays auto-converted and simply becomes unreachable.

| Go API | Managed realization | Divergence |
|---|---|---|
| `Gosched()` | `Thread.Yield()` | none — same "offer the rest of the slice, then continue" contract |
| `GOMAXPROCS(n)` | remembered value, defaults to `Environment.ProcessorCount`; `n < 1` queries | does **not** cap parallelism — the CLR schedules goroutine threads. The universal save/restore idiom `defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(n))` is exactly right |
| `GC()` | blocking compacting collect → `WaitForPendingFinalizers` → collect | none observable; the second pass reclaims what finalizers released, which is the state a completed Go cycle leaves |
| `Stack(buf, all)` | managed `StackTrace` text into `buf` | cannot show frames that already unwound, and `all` reports only the calling thread (see below) |
| `ReadMemStats(m)` | `GC.GetGCMemoryInfo`/`GetTotalMemory`/`GetTotalAllocatedBytes`/`GetTotalPauseDuration` | allocator-internal fields (`Mallocs`/`Frees`/`HeapObjects`/`BySize`, the pause histories) stay **zero** rather than invented |
| `LockOSThread`/`UnlockOSThread` | no-ops | no-ops **by construction**: a goroutine already *is* a managed thread, so the guarantee they exist to provide holds unconditionally |

`Stack`'s divergence is worth stating precisely because it looks like a bug: Go keeps the panicking
frames alive until the panic completes, so `debug.Stack()` called from a deferred function *during* a
panic shows the panicking stack; a CLR exception pops those frames before the `finally` that runs the
defers, so the managed trace shows the deferred frame's stack instead (sync's
`TestOnceFuncPanicTraceback`).

**`runtime/debug`'s knobs are the same ruling one package over.** `runtime/debug/stubs.go` declares
`setGCPercent`/`setMemoryLimit`/`setMaxStack`/`setMaxThreads`/`setPanicOnFault`/`readGCStats`/
`freeOSMemory` with no body ("Implemented in package runtime"), bound by the Go linker to a runtime
function carrying the matching `//go:linkname … runtime/debug.<name>` **PUSH**. go2cs has no
cross-assembly linker, so each became a throwing stub — `debug.SetGCPercent`, the first line of
sync's `TestPool`. Forwarding to the converted runtime bodies would not help (they take the mheap
lock on the system stack and wait on a mark cycle), so `runtime/debug/stubs_impl.cs` answers the
contracts: the tuning knobs keep Go's documented GET/SET semantics (remember, return the previous
value, negative = query where Go says so) and have no effect on collection; `freeOSMemory` is a real
compacting collect; `setPanicOnFault` is `[ThreadStatic]` because it is per-goroutine in Go;
`readGCStats` reports the counters the CLR really has (collection count, total pause) and an **empty**
per-pause history in the exact packed layout `ReadGCStats` unpacks. `modinfo`/`WriteHeapDump`/
`SetTraceback`/`runtime_setCrashFD` are inert, matching a binary built without module or heap-dump
support.

**Two assembly primitives DO have exact managed forms** (`runtime/stubs_impl.cs`).
`systemstack(fn)` is `fn()` — Go's own contract already says that a caller already on a system stack
"calls fn directly and returns", and in the managed model there is one stack per goroutine and no g0
to switch to, so that is the only branch. `procyield(n)` is `Thread.SpinWait((int)n)`. Everything
else in `runtime/stubs.go` deliberately keeps throwing, `getg`/`mcall` included: a loud, locatable
failure beats quietly operating on a fabricated goroutine descriptor.

**`internal/runtime/atomic` is the NATIVE half of the S1 fork and gets a real conversion, not a
stub** (`atomic_impl.cs`). Its ~40 declarations are all `.s` files, but they are plain memory atomics
over native scalars, and the CLR has an exact equivalent: `Xadd*`→`Interlocked.Add` (both return the
NEW value), `Xchg*`→`Interlocked.Exchange` and `And32`/`Or32`/`And64`/`Or64`→`Interlocked.And`/`Or`
(all return the OLD value), `Cas*`→`CompareExchange`, `Store*`/`StoreRel*`→`Volatile.Write` (a
release store in both models). Two widths have no intrinsic and take a shared latch instead: 8-bit
(`And8`/`Or8` — the CLR has no byte-width `Interlocked`) and the `unsafe.Pointer` family
(`Casp1`/`StorepNoWB`/`storePointer`/`casPointer` — golib models `unsafe.Pointer` as a class wrapping
a `uintptr`, so a CAS must compare the wrapped NUMBER and swap the REFERENCE, which no single
intrinsic expresses); `Xadduintptr`/`Anduintptr`/`Oruintptr` ride a CAS loop because `Interlocked`
offers `Exchange`/`CompareExchange` for `nuint` but not `Add`/`And`/`Or`. Leaving this package
stubbed poisons everything built on it — `runtime.SetMutexProfileFraction`, an otherwise perfectly
faithful conversion, died on `Store64` (sync's `TestMutex`).

**`unsafe.Pointer(uintptr(0))` must compare equal to `nil`.** The converter bridges every
`unsafe.Pointer`-valued call through `uintptr`, because `unsafe` lives in its own assembly and can
carry no implicit conversion on the core pointer class. golib's round-trip therefore has to preserve
nil in *both* directions, and it did not: `Pointer(uintptr)` produced a non-nil box wrapping 0, and
`(uintptr)ptr` dereferenced a nil-constructed box instead of yielding 0. The symptom was silent and
far away — sync's `poolDequeue` reads each ring slot's `typ` word to decide whether it is free, every
empty slot came back "occupied", `pushHead` returned false forever and `TestPoolDequeue`/
`TestPoolChain` spun. `unsafe.cs` now marks the zero address nil (a protected `ж<T>(value, isNull)`
constructor holds the address *and* the nil flag) and tolerates a nil box on the way out.

**`runtime.Goexit` unwinds the goroutine with a `GoexitException`, and every one of Go's three
properties falls out of machinery that already existed.** Go specifies that Goexit ends the calling
goroutine only: its deferred calls all run, `recover()` inside them returns **nil** (a Goexit is not a
panic, and a defer cannot cancel it), and other goroutines are untouched. The managed form is a golib
`GoexitException` that is deliberately **not** a `PanicException` — `recover()`'s implementation keys
on `PanicException` (the frame's `GoFrame.IsPanic` filter, via `RuntimeErrorPanic.TryAsPanic`), so it
is blind to this type BY CONSTRUCTION and the recover path needs **zero** special handling. The defers
still run because `GoFrame.Run()` sits in a `finally`, draining the defer list during the unwind
exactly as it does for a panic, across frames. Every `go` statement dispatches through one **goroutine root**
(`golib.Goroutine.Start` → `Run`, the single site all 18 `builtin.goǃ` arity overloads funnel into),
which catches `GoexitException` and ends that thread silently; a `PanicException` reaching the same
point is deliberately NOT caught and keeps its Go-faithful fatal path (stderr report, exit 2 — guarded
by the `GoroutinePanicExitCode` behavioral test). `runtime.Goexit`'s body is hand-owned in the runtime
package's `managed_impl.cs` (`manualConversionFuncs`), since the converted body drives Go's own
`_panic` record and stack unwinder (`getcallerpc`/`nextDefer`/`goexit1` — all assembly). Guarded by
the `GoexitDefers` behavioral test (defers run across frames, `recover()` sees nil, other goroutines
still run, main continues) and by sync's `TestOnceFuncGoexit`, which this unblocked.

**Goexit from the MAIN goroutine stays gated — at runtime, not statically.** There, Go ends `main`
without returning while the *program continues* running its other goroutines, crashing with
"no goroutines" once they all exit; that needs a live-goroutine registry and a main-thread parking
protocol the managed model does not have (`docs/phase4/DESIGN-goexit.md` option C). The distinction is
not statically decidable — a function's call graph says nothing about which goroutine will run it — so
`Goroutine.OnGoroutine` (a `[ThreadStatic]` the root sets and restores, because goroutines run on
pooled threads) answers it at the call, and the main-goroutine case throws a loud
`NotSupportedException` naming the design doc rather than silently doing something else. Consequently
`unsupportedRuntimeCapabilities` (`testConversion.go`) is now **empty**: the mechanism remains — a test
whose transitive closure reaches a listed symbol converts as `unsupported`, disclosed by name, the
same gate an unsupported `testing.*` member uses — with `TestUnsupportedRuntimeCapabilityGate` as its
positive control so an empty list cannot masquerade as a working lookup. Add an entry only for
something *provably* unavailable, never merely unimplemented, and scan every validated package for the
symbol first: gating one **removes** tests from a banked package's run set, the mirror of the widening
trap.

**`-test-timeout` is the PACKAGE deadline, and it reaches both sides.** The flag used to bound only the
child *process*, leaving `go test` and the converted host each on its own **10-minute** default — so no
value of it could let a slower-than-Go suite finish. `hash/maphash` self-terminated at exactly 600 s
under `-test-timeout 40m`, reporting its still-running `TestSmhasherAvalanche` as an empty verdict that
reads exactly like a real failure (the very "slow ≠ hung" trap, one layer below where it was known).
The compare/run actions now pass the flag through as `go test -timeout` **and** the host's own
`-timeout`, and kill the child one minute later (`testChildTimeoutGrace`) purely as a safety net — the
in-process deadline must fire first, because it is the side that writes results. Threading it also
exposed that the host's duration parser accepted only a *single* unit, rejecting `time.Duration`'s own
`String()` output (`30m0s` → "invalid Go-style duration", exit 2 before a test ran); it now parses Go's
real grammar, a sequence of decimal-and-unit pairs over ns/us/µs/ms/s/m/h, so `1h30m` works as it does
for `go test`. maphash's C# suite needs ~15 min where Go's needs 7.6 s — a performance gap, recorded as
such, not a correctness one.

**The test host treats an escaping `GoexitException` as Go's `tRunner` does**, and each test's
dedicated thread is marked a goroutine (`Goroutine.Enter`) because in Go a test body IS one. Go's
`FailNow` is *specified* as "mark failed, then `runtime.Goexit`", so a Goexit escaping a test body
means the test ended without completing — Go reports `errNilPanicOrGoexit` ("test executed panic(nil)
or runtime.Goexit") against that test. The host logs exactly that text and fails the one test, where
Go additionally panics the whole binary; keeping the run alive leaves the rest of the package
measurable. This is also the path `testing.T.FailNow` fidelity will take.

**An unhandled NON-panic exception on a goroutine fails ONE test instead of killing the host — and
that containment is TEST-HOST-ONLY.** A converted program keeps Go's fidelity (an unhandled failure in
a goroutine is process death), which is golib's default: nothing contains it and it reaches the
AppDomain backstop. A host that runs many independent Go programs in one process is different — the
crash discarded every result not yet written and blanked the tail of the package, so one defect read as
a mass infrastructure wall. `Goroutine.ContainUnhandledExceptions(policy)` lets such a host install a
containment policy, which golib consults through an exception **filter** (never a catch-and-rethrow, so
the uncontained path is bit-for-bit the old behavior — unhandled, stack intact, intervening `finally`
blocks unrun). A panic is never offered to a policy. The host attributes the failure to the right test
with an `AsyncLocal` set on the test thread: it flows with the `ExecutionContext` that `Thread.Start`
captures, which is exactly how golib dispatches a goroutine, so the attribution survives goroutines
spawning goroutines. If the crashed goroutine was the one that would
have unblocked its test, that test now waits for the package timeout — which still writes every result
gathered so far, where the crash wrote none.

**A PANIC on a goroutine still kills the host — but it no longer takes the run's evidence with it
(2026-08-14).** Containment stops at panics deliberately (above), so the fatal path stayed: golib's
backstop printed `panic: <value>` and exited 2. For a converted PROGRAM that is right and complete —
it is Go's own report. For a host it was the hardest possible diagnostic: no frame, no test name, and
every verdict already gathered discarded unwritten. `net/rpc/jsonrpc` is the case that made it
concrete — a `reflect`-allocated `[1]int` reply panicked on an rpc goroutine, the host died, and the
package recorded **0** verdicts where it had been recording 6, so a strictly BETTER corpus read as a
worse one and the one real failure read as three.

`Goroutine.ObserveUnhandledPanic(observer)` is the other half of the containment pair, and it
contains nothing: the observer runs from an exception filter that **always declines**, so the panic
still reaches the backstop with its stack intact and the intervening `finally` blocks unrun —
byte-identical to an unobserved run, which is what keeps Go's fidelity and the differential oracle's
view of it. What the filter buys is the moment BEFORE the unwind. The test host uses it to attribute
the panic to the test whose goroutine it was (the same `AsyncLocal`), report that test's terminal
`fail` carrying the full traceback (`PanicException.StackTrace`, which prefers the panic's ORIGIN over
the frames it unwound through), and flush the result and JUnit files. A goroutine panic now costs the
TAIL of a run rather than all of it — which is also what `go test` shows, since a Go binary that dies
this way has already streamed the verdicts it reached. Guard:
`GolibTests/GoroutineRootPanicTests`, over the root's whole policy — a panic observed and still
escaping, its fault site surviving, a mapped runtime-error panic, a non-panic exception still going to
containment and not to the observer, and `Goexit` reaching neither.

**The unhandled-exception backstop prints the whole exception chain for a NON-panic failure.** A real
panic keeps Go's report shape (`panic: <value>` on stderr, exit 2). Anything else is a *defect to
diagnose*, and `ex.Message` alone threw the evidence away: a `TypeInitializationException`'s own
message merely names the type and says "see inner exception", so the actual fault and its stack were
lost (a whole `gob` run's real cause was invisible this way). The backstop now writes `ex.ToString()`
for the non-panic case, carrying the full inner-exception chain and stacks.

### `sync.Pool` — a managed-reference ring slot, and a thread-affine stand-in for the P pin

`sync.Pool` is the third shape of the same wall, and the most instructive: **the raw-metal type is not
a pointer-in-an-integer, it is the `any` itself.** Go's `poolDequeue` is a lock-free ring of `eface`
slots — the two-word `{type, value}` form of an interface — and its whole ownership protocol hangs on
the TYPE word: a slot is empty **iff** `typ == nil`, and a consumer publishes "done with this slot" by
atomically storing nil into `typ` alone, leaving `val` to be overwritten later. Under the CLR an `any`
is ONE reference, so the literal conversion reinterprets the two-word struct as an `any`
(`Unsafe.As`) and the `typ` word ends up doing double duty as both the type tag and the value. Two
failures follow immediately, and both were observed: a stored value read back through the
reinterpretation surfaces as its own type word (`panic: interface conversion: interface {} is
unsafe.Pointer, not int`), and the empty-slot sentinel — a nil `unsafe.Pointer` — is indistinguishable
from a *stored value of that type*, so `pushHead` and `popTail` disagree about who owns a slot and the
ring corrupts or wedges. `TestPoolChain` took the whole test host down with the panic above, which cost
the 14 tests that sort after it alphabetically.

The fork is confined to the **slot representation** and keeps every other line of Go's algorithm —
the packed `head`/`tail`, the fullness test, the CAS protocol, the single-producer/multi-consumer
contract, and the entire `poolChain` half:

```csharp
// eface is Go's two-word {type, value} representation of an `any`. Under the CLR an `any` IS a single
// managed reference, so the slot holds that reference directly.
[GoType] partial struct eface {
    internal any? val;
}
```

`null` is the empty-slot sentinel — a state no stored value can forge — and a private singleton stands
in for Go's typed-nil `dequeueNil(nil)` marker (a slot holding a *nil interface value* must still read
as occupied). That also collapses Go's two-step release into one: with a single word there is nothing
to tear, so **one** `Volatile.Write(ref slot.val, null)` both clears the value and hands the slot back
to the producer, where Go needs a value store followed by a publishing `atomic.StorePointer` on `typ`.
`TestPoolDequeue` proves the release protocol end to end — 2·10⁶ items through a **fixed** 16-element
ring, with the head/tail seeded 500 short of wrapping.

`Pool` itself is a whole-file hand-own for a different reason: its `[P]poolLocal` shard block is
reached by **pointer arithmetic** through an `unsafe.Pointer` (`indexLocal`), which is meaningless when
the block is a managed array, and `procPin` — the thing that gives each shard's dequeue its *single*
producer — has no P to pin to. Go's algorithm survives intact (private slot → the shard's shared chain
→ steal from other shards' tails → the victim cache; `poolCleanup` ageing local → victim → dropped);
three pieces are replaced:

| Go mechanism | Managed realization | Divergence |
|---|---|---|
| `[P]poolLocal` block + `indexLocal` pointer arithmetic | a `poolLocal[]` held directly, one heap object per shard | the cache-line pad against false sharing is gone — separate objects, nothing to pad |
| `procPin()` → P id, preemption off | **thread-affine** index: a thread draws a sticky id once, in arrival order, folded into the current `GOMAXPROCS`; `procUnpin` has nothing to undo | threads outnumber shards, so two threads CAN share one shard — the one thing a real pin rules out. Closed on Pool's side: the private slot is claimed/taken with a single `Interlocked` step, and the shard's shared-chain HEAD (single-producer by contract) is serialized by a per-shard producer gate. Stealing (`popTail`) stays lock-free, as designed |
| `poolCleanup` at the start of every GC cycle, world stopped | registered with the runtime exactly as Go registers it (`runtime_registerPoolCleanup` → `runtime.GC()` invokes the hook, mirroring `gcStart` → `clearpools`) | narrower trigger: **requested** collections age the pool, automatic CLR collections do not, so a program that never calls `runtime.GC()` retains its cached items longer than Go's would. And the swap runs on the caller's thread, not under STW, so a `Put` racing it can land in a shard that just became a victim and age one cycle early. Both sit inside Pool's contract — *any item may be removed at any time* — so they cost a cache hit, never correctness |

The registration path is worth noting because it is a **general** cross-assembly constraint, not a Pool
detail: sync reaches the runtime through `//go:linkname runtime_registerPoolCleanup`, whose target
`sync_runtime_registerPoolCleanup` the exported-ness rule makes `internal` to the runtime assembly — and
[a linkname forwarder cannot bind an internal target across assemblies](#a-cross-package-golinkname-pull-emits-a-forwarder-not-a-throwing-stub).
A one-line `public` shim in `runtime/managed_impl.cs` is the crossing point, the same remedy
`blockUntilEmptyFinalizerQueue` already uses in `mfinal.cs`.

**What the arc could NOT satisfy, stated precisely.** `TestPoolGC` asserts that after draining a Pool
and collecting, at least `N-1` of `N` finalizers have run — a budget of exactly **one** straggler still
reachable "on stack or elsewhere". A drained Pool here holds nothing (proven with a `WeakReference`
probe: run the drain in its own frame and **zero** of 100 survive), but the test's own frame spends the
budget twice under an **unoptimized** build: the fill loop's `v` keeps the last item, and the JIT's
MinOpts codegen keeps the *discarded* return value of the final `Get()` reachable from the calling
frame for the rest of the method. That second straggler reproduces with no Pool in sight — a factory
whose result is discarded in a loop leaves its last object alive in Debug — and the test passes in
`Release`, where the JIT's liveness is precise. So it is a codegen-liveness divergence of the
Debug-configured CLR, not a Pool defect; the honest classification is the disclosed-divergence class,
not a contortion of the drain order to make one assert land. That class is now
[named and pinned](#codegen-liveness--a-frame-holds-what-go-has-already-dropped) as
`codegen-liveness`, with `TestPoolGC`'s straggler count pinned exactly so a real Pool retention
regression cannot hide behind the disclosure.

### A pointer's REFERENT, not its box, answers every lifetime and identity question

A `ж<T>` is a *pointer*, and go2cs mints them freely: `Ꮡ(s, i)` allocates a fresh box on every call,
and `Ꮡx.of(T.Ꮡfield)` allocates one per field access. The box is therefore an **expression
temporary** whose lifetime says nothing about the storage it names. Anything that asks a question
about the *object* — when does it die, is this the same object — must ask it of the referent.
`INilPointer.ReferentObject` (golib `ж.cs`) is that projection, and `ж<T>` resolves it the way
`Equals` already resolves pointer identity:

| Pointer shape | `ReferentObject` |
|---|---|
| array/slice element (`&s[i]`) | the canonical backing storage (`CanonicalElement` — the `T[]`, never a per-call header/view) |
| struct field (`&x.f`), including a nested `of()` chain | the **root** allocation, resolved recursively through the per-call intermediate boxes |
| standard heap box (`&x`, `new(T)`), whatever the pointee's type | the box itself — it *is* the allocation |
| native alias (a `uintptr` round-trip) | the box itself — the address it wraps names no *managed* allocation, so there is nothing GC-keyed to resolve to (the one place this projection and `Equals`, which identifies such boxes by that address, part company) |

Two consumers depend on it, and both were broken without it:

* **`runtime.SetFinalizer`** keyed its `ConditionalWeakTable` registration on the boxed `obj`. Go
  attaches a finalizer to the *object* a pointer points at — `runtime.SetFinalizer(&buf[0], f)`
  finalizes `buf`'s allocation — so keying on the throwaway `ж<byte>` the argument expression
  allocated registered against a lifetime nothing in the program shared: the finalizer became due
  the moment the box died (or, under a JIT that roots the whole frame, could never become due at
  all). It now keys on the referent, so the registration tracks exactly the allocation Go would
  finalize, and two boxes for the same address correctly share one registration (Go's "finalizer
  already set").
* **`sync.Cond`'s `copyChecker`**, below.

**`Ꮡ(IArray<T>, index)` takes its target BY VALUE, deliberately.** It used to be `in IArray<T>`.
`in` on an *interface* parameter elides no copy — it is already one reference — but it forces the
caller's boxing temp (a `slice<T>`/`array<T>` header is a struct, so every call boxes one) to be
**address-exposed**, and an address-exposed slot is not lifetime-tracked: the JIT reports it live for
the whole enclosing method. One `Ꮡ(s, i)` therefore pinned `s`'s backing array to the caller's frame
until that method *returned*, in fully optimized code. Measured with a `WeakReference` probe against
a `DOTNET_TieredCompilation=0` build: the `in` form leaks the array, the by-value form releases it.

### `sync.Cond`'s copy detector, on reference identity rather than an address

Go's `copyChecker` is a `uintptr` that stores **its own address** and compares:

```go
if uintptr(*c) != uintptr(unsafe.Pointer(c)) &&
   !atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c))) &&
   uintptr(*c) != uintptr(unsafe.Pointer(c)) {
	panic("sync.Cond is copied")
}
```

Neither half survives conversion, and the two failure modes point in opposite directions:

* Storing an **address** is unsound on a moving collector. The GC relocates the box holding the
  `Cond`, so a compaction between two `Wait` calls would leave a stale word in a `Cond` nobody
  copied — a *spuriously panicking* condition variable, which is far worse than no check.
* The auto body is **inert**. The address-of-self operand converts to
  `@unsafe.Pointer.FromRef(ref c)` while the CAS destination converts to `Ꮡ((uintptr)(c))`, which
  boxes a **copy** of the value — so the compare-and-swap writes to a throwaway box, the checker is
  never initialized, and `check()` never panics however often the `Cond` is copied. `TestCondCopy`
  failed with `got <nil>, expect sync.Cond is copied`.

`cond_impl.cs` (registered as `manualConversionFuncs["sync"]["copyChecker.check"]`, so only this one
method is hand-owned — the `copyChecker` type and every `Cond` method stay auto) asks the same
question against the invariant the CLR does guarantee: the checker stores a token derived from its
`ReferentObject`'s GC-stable identity hash. Every `Cond` method reaches its checker as
`Ꮡc.of(Cond.Ꮡchecker)`, whose referent is the *root* allocation holding the `Cond` — the same object
on every access, including for a `Cond` embedded in a larger struct, which is exactly why the
referent projection must recurse. A struct copy carries the ORIGINATING allocation's token into a new
one, which is what `check()` sees.

Two deliberate simplifications, both in the safe direction:

* **No compare-and-swap.** Go needs one because concurrent first-users race to publish the word;
  here every racer computes the *same* token for the same allocation, so an aligned native-word store
  cannot publish a value any racer would disagree with.
* **Identity hashes are not unique**, so a copy between two colliding allocations goes unreported —
  a missed detection, never a false alarm. Likewise, two elements of one backing array both resolve
  to that array, so a copy between them is invisible (an element pointer's identity is the storage
  plus an index the checker has no room for). Recorded, not worked around: Go's own check is
  documented as best-effort.

### `runtime.Stack` renders a GO-shaped traceback, and recovers the panic site

A traceback is **observable output**: Go programs print it on a crash, and Go's own tests grep it by
package-qualified function name (`sync`'s `TestOnceFuncPanicTraceback` looks for
`sync_test.onceFuncPanic`). Two things made the CLR trace unusable for that.

**Frame names.** A converted package's frames live on a `<pkg>_package` class inside namespace `go`,
so the CLR renders `at go.sync_test_package.onceFuncPanic() in …oncefunc_test.cs:line 191` — which
contains `sync_test_package.onceFuncPanic`, never `sync_test.onceFuncPanic`. `Stack` now formats
frames itself, in Go's shape (`<pkg>.<Func>()` then a tab-indented `<file>:<line>`), mapping
`go.<a>.<b>_package` → `<a>/<b>` (Go names a package by its import path, which the namespace mirrors)
and a closure's `<Outer>b__N` on its display class → `Outer.funcN`, Go's own spelling for a function
literal. A frame that is **not** converted Go code — golib, the BCL, the test host — keeps its .NET
name rather than being given an invented Go one, and Go's `+0x<offset>` PC deltas are omitted.

**Frames that already unwound.** Go keeps a panicking goroutine's frames physically on the stack until
the panic completes, so a `debug.Stack()` inside a deferred function shows the panic site; a CLR
exception has unwound them before the `finally`-based defer runs. Worse, *both* ways a panic travels
destroy the trace: re-raising the same instance (`throw ex`) resets `Exception.StackTrace` to the
re-raise point, and Go's re-panic idiom — `defer func(){ p := recover(); panic(p) }()`, which is
precisely how `sync.OnceFunc` replays a panic on every call — creates a brand-new panic in the
deferred frame. So golib snapshots the origin **once**, at the first (innermost, deepest) catch, into
`PanicException.PanicTrace`, and a panic raised while handling another *inherits* it. `GoFrame.Run()`
tracks which panic a deferred sequence is handling in a strictly save/restore-scoped thread-local
(`HandledPanic`, surfaced as `GoFuncRoot.InFlightPanic`) — `recover()` clears `CapturedPanic`, but
Go's traceback keeps showing the panicking frames for the rest of the sequence, and the strict scoping
is what stops the value from ever going stale. `Stack` appends those frames *below* the live ones,
which is where Go's traceback puts them too, since the deferred call runs on top of `gopanic`. Cost on
the non-panicking path is zero: the CLR fills the trace at throw time anyway, and nothing is
snapshotted unless a panic is actually caught.

### EVERY reader of a panic's trace gets the origin — not just `runtime.Stack`

The snapshot above served exactly one consumer. Every *other* reader — the Phase-4 test host, an
unhandled-exception dump, a debugger — asked `Exception.StackTrace` and got the wreckage the section
above describes, because a panic reaches its reporter by being re-raised from `GoFrame.Run()`
(`throw CapturedPanic.Value`, once the deferred sequence declined to recover it) and re-raising a
stored instance resets the trace to the re-raise point. So **every panic in the corpus reported the
same deepest frame — the defer drain — regardless of where it actually faulted.** That reads as
a defect in the defer machinery, and it hid the real one: nine of `time`'s failures were filed as a
shared "nil pointer dereference in the defer drain" when they were one nil-receiver deref in
`Location.lookup` (see the normalization idiom above), invisible until the trace was honest.

Two changes, both at the layer that owns the fact:

1. **`PanicException.StackTrace` reports the panic SITE first, then the frames it unwound through** —
   the closest a CLR exception can come to Go's single uninterrupted traceback. `PanicTrace` already
   held the origin; overriding the property is what lets consumers see it without any of them knowing
   the panic machinery exists. With no origin recorded the base trace is returned unchanged, so the
   override only ever *adds*.
2. **The origin is snapshotted at the ADOPTION POINT, `RuntimeErrorPanic.TryAsPanic`** — the one place
   a .NET exception becomes a Go panic. A mapped runtime error (nil deref, divide by zero) is
   synthesized there and was never thrown at the fault, so only the incoming exception carries those
   frames; once `TryAsPanic` returns, they are gone. Doing it in each *adopter* covered only panics
   that passed through a frame — a function that never defers gets no frame, so its fault
   travelled raw to the reporter, which synthesized a brand-new panic with **no trace at all**. That
   was five of the nine `time` rows: `panic: …` and nothing else.

(Guarded by `GolibTests.PanicTracebackTests`: a synthesized runtime-error panic escaping a `GoFrame`, an
explicit `panic()` surviving the re-raise, the same runtime-error panic adopted with **no** frame
anywhere between fault and reporter, a recovered panic that must not be reported at all, and a panic
with no origin snapshot falling back to its intact base trace. Not a behavioral test: no converted Go
program reads a CLR stack trace, so there is nothing to output-compare — the Go-observable half is
`runtime.Stack`, covered above.)

### `runtime.Callers` / `Frames.Next` walk the managed stack projected to GO-LOGICAL frames

Go's programmatic traceback — `pc := make([]uintptr, N); n := runtime.Callers(skip, pc)` expanded by
`runtime.CallersFrames(pc[:n])` — bottoms out in machinery with **no managed form**: `Callers` →
`callers()` opens with `getcallersp()`/`getcallerpc()` (assembly intrinsics) and hands the raw stack
to the runtime `unwinder`, and `(*Frames).Next` resolves each PC through `findfunc`'s linker-built
`funcInfo` tables. Converted faithfully they compile, then die on the first step — `io`'s
`TestMultiReaderFlatten`/`TestMultiWriterSingleChainFlatten` (relative stack-depth asserts, the
flatten optimization's only observable) threw `getcallersp: … not implemented` from inside
`runtime.Callers` itself.

The fork is the PROCESS-CONTROL one (section above): both API **contracts** — "record the calling
goroutine's frames as opaque PCs" and "expand PCs to function/file/line" — the CLR answers natively,
so `Callers` and `Frames.Next` are hand-owned in `runtime`'s `managed_impl.cs`
(`manualConversionFuncs["runtime"]`) over `System.Diagnostics.StackTrace`, while **`getcallersp`
stays an honest `NotImplementedException` stub**: a caller's stack pointer has no managed answer, so
the chain is severed at the API boundary that does — the same severing rule the reflection bridge
applied at `methodName`. `CallersFrames` itself is pure Go (slice bookkeeping) and stays auto.

What makes the projection *semantically* faithful is the **Go-frame filter**: a managed frame counts
only when it is a function the **Go source declares** — a free function or `[GoRecv]` receiver on a
`go.*` `<pkg>_package` class, a method on a struct nested in one, or a function literal (its compiler
display class nests in the same scope). go2cs **dispatch machinery is invisible**, exactly as Go's
interface dispatch adds no frame: generated adapter shells (any `IGoAdapter`) and go2cs-gen's
synthesized forwarders (`[GeneratedCode("go2cs-gen", …)]`, e.g. RecvGenerator's `ж`-overloads) are
dropped, as is everything outside a package class (golib, the BCL, the test host). Without the
filter, one Go-level interface call contributes 2–3 CLR frames and every relative-depth assert is
off; with it, **depth deltas between two `Callers` calls on one goroutine match Go's logical model**
— `io`'s `readDepth == myDepth+2` holds bit-exactly. Absolute depth still reflects the managed
host's own frames below `main`/the test runner, which cancel in any same-goroutine delta.

PC values are **opaque interned tokens** (index+1 into a process-lifetime record table keyed on
module × method-token × IL offset; 0 stays the invalid sentinel), never addresses — stable across
recurrences so pc-equality behaves as in Go, resolvable by any later `CallersFrames` walk, like Go's
permanent PC space. `Frame.Function` uses the Go spelling (`goFrameName`: `io_test.TestCopy`,
`runtime/debug.Stack`, `Outer.funcN` for literals); `Frame.File`/`Line` name the **converted `.cs`**
source — the source that honestly exists; `Frame.Func` stays nil (contract-permitted) and
`FuncForPC` remains a stub, since a `*runtime.Func` has no managed referent. A PC the runtime never
minted resolves like Go's `!funcInfo.valid()` — skipped, not fatal — and `more` is computed
precisely over the remaining *resolvable* tokens, mirroring Go's two-frame prefetch. Known fidelity
edge, recorded: Go's `<autogenerated>` promoted-method wrappers can occupy PC slots in a raw Go
`Callers` capture, while here **all** synthesized wrappers (Go-side and go2cs-side alike) are
uniformly invisible — refine only if a consumer differential ever lands on it.

**`runtime.Caller` works by severing the FUNNEL, not by hand-owning another public API
(2026-08-07).** The 2026-07-31 landing above hand-owned the exported `Callers`, which left
`runtime.Caller` — the far more widely used of the pair — still dead: its auto body calls the
*lower-case* `callers`, and that is the declaration whose first statement is `getcallersp()`. Four
call sites go through it (`Caller`, `mprof`'s profile recorders, `proc`'s `createstack`,
`tracestack`), so `callers` is the single funnel and hand-owning **it** is one entry on
`manualConversionFuncs["runtime"]` that fixes all four — and leaves `Caller` auto-converted and
Go-shaped, which hand-owning `Caller` would not. Go's own comment on the declaration says the same
thing from the other side: "almost identical to `Callers`", linkname'd by the ecosystem, signature
frozen — an API boundary with a managed answer.

The two differ in exactly one way that the managed walk has to encode: `Callers(0, pc)` names *its
own* frame, while `callers(0, pcbuf)` names *its caller's* (Go's body starts the unwinder at
`getcallerpc()`/`getcallersp()`, one frame up). The walk therefore lives in a private
`captureCallers`, and each entry point passes its own frame budget — `Callers` → `skip + 1`,
`callers` → `skip + 2` — because **every hop is itself a Go-source frame by the filter above**: they
are all declared on `runtime_package`, so the walker cannot exclude them structurally. All three
carry `[MethodImpl(MethodImplOptions.NoInlining)]`: the CLR's `StackTrace` does not report inlined
frames where Go's unwinder does (through the compiler's inline trees), so an inlined hop would
silently shift every answer by one. Guarded by the `RuntimeCallerFrames` behavioral test, which
asserts *relations* between frames rather than absolute positions — chiefly that `callerLine()` and
`wrapGrand()` invoked **on one source line** must report the same line, an equality that breaks
under an off-by-one in either direction (positive control: `skip + 1` in `callers` flips three of
its eleven output lines). The suite builds Debug, where nothing is inlined, so the guard was also
run against a **Release** build of the same program — identical output, so the pins hold under the
optimizing JIT and not merely under the one that could not have broken them. `io`'s banked flatten
asserts re-validate at 59/59 across the refactor.

**What `Caller` can and cannot honor.** `pc` is the same opaque interned token `Callers` mints, and
`ok` is exact — including the past-the-stack case, which returns Go's zero values with `ok == false`
rather than inventing a frame. Measured against a Go control on the two things a caller actually
does with a `pc`: `runtime.CallersFrames([]uintptr{pc}).Next()` — the branch `log.Output` takes when
a `pc` is supplied — **resolves** to the same file/line `Caller` returned, while
`runtime.FuncForPC(pc)` returns **nil**, which is Go's own `!funcInfo.valid()` answer rather than an
error or a fabricated `*Func` (the inverse-atomic rule: a `*runtime.Func` has no managed referent, so
none is minted). One fidelity edge in the same probe: `Frame.Function` renders the *emitted* method
name through `goFrameName`, so the program's own top frame reads `main.Main`, not Go's `main.main` —
the same "a candidate's EMITTED name is not always its GO name" class recorded elsewhere.
`file`/`line` name the **converted `.cs`** position, consistent with
`Frame.File`/`Line` above and with the honest-answer rule: the running program's source *is* C#, and
no Go-position map is emitted today. That fully serves a caller asking "where am I" — `log`'s
`Lshortfile`/`Llongfile` prefix produces `log_test.cs:67: hello 23 world` where Go produces
`log_test.go:63: …`, and `testing/slogtest`'s `withSource` labels its cases with a real file:line
instead of panicking in a package initializer. It does **not** serve a test that asserts the *Go*
file's own geometry: `log`'s `TestAll` pins `` `.*/[A-Za-z0-9_\-]+\.go:(63|65):` `` — the `.go`
extension and the exact line numbers of the `Printf`/`Println` calls inside `log_test.go`. That is
**not** a disclosure candidate: unlike `alloc-profile` or `codegen-liveness` it is not unsatisfiable
at any layer go2cs owns — a Go-source position map (emitted `#line` directives, or a side-car map
consulted by `internCallerFrame`) would satisfy it exactly. It is a deferred capability, and `log`
stays unbanked until one lands rather than being disclosed around.

A second disclosed-divergence class alongside `alloc-profile`, first pinned by `sync` (packages
`TestOnceXGC` ×3 subtests and `TestPoolGC`). Go's GC consults **per-safepoint liveness maps**: a
local dies at its last use, even in the middle of a running function. The CLR's GC info is
conservative in two ways that a GC-lifetime test can see from inside its own frame:

* **Tier-0 / MinOpts codegen reports every frame local live for the whole method.** Every test method
  runs *once*, so it is jitted at tier 0 — in Release as well as Debug — and the pipeline builds the
  test host unoptimized on top of that. Measured: a bare `byte[]` local is still alive after a full
  blocking collect, and becomes collectible under `DOTNET_TieredCompilation=0`.
* **A by-value struct argument larger than a machine word is passed by hidden reference.** A
  `slice<T>` is four words, so the x64 ABI makes the caller materialize a stack temp and pass its
  address — address-exposed, therefore untracked, therefore reported live for the whole frame. This
  one holds in **fully optimized** code, which is what makes it provable rather than a build-flag
  artifact: a probe frame that registers a finalizer on `buf`'s storage releases it (finalizer fires)
  when nothing else touches `buf`, and pins it for the frame's lifetime when the frame merely passes
  `buf` by value to one function.

`TestOnceXGC` is unsatisfiable on the second point *at every layer go2cs owns*: its own body is
`f := fn(buf)`, and both `gcwaitfin()` checks happen inside that frame. `sync.OnceFunc` genuinely
does drop the wrapped function — measured directly, the backing array is released after the first
call — so the disclosure covers the CLR's frame conservatism, not a retention bug. The two real bugs
the investigation *did* find (SetFinalizer keying on the pointer box; `Ꮡ`'s `in` parameter pinning the
array) were fixed at their layers first; only what remained was disclosed.

### The process ROOTS a converted program never gets from a Go bootstrap: `runtime.envs`, `os.runtime_rand`

Two of Go's cheapest facts about a running process arrive through machinery conversion cannot carry:
the environment is copied into `runtime.envs` by `goenvs()` during `schedinit`, and the temp-file name
source is `runtime.rand`, a per-M chacha8 PRNG the `os` package reaches by `//go:linkname`. Neither
producer survives — `schedinit` is Go's scheduler bootstrap and go2cs never runs it (every converted
runtime `init` carries the emitted comment *"not run; .NET is the runtime"*), and `runtime.rand` is
one more bodyless assembly declaration the [`PartialStubGenerator`](#source-generators) fills with a
throw. Both are supplied by hand-owned `*_impl.cs` companions with no `.go` counterpart, so a
reconvert never touches them.

**`runtime.envs` — `core/runtime/goenvs_impl.cs`.** `gogetenv`'s first act is to reject a nil
`environ()` with `throw("getenv before env init")`, so an unpopulated `envs` did not degrade the
lookup, it *killed the caller* — and then re-faulted inside its own traceback on the unimplemented
`getcallerpc`, presenting as a crash rather than as a missing value. The reach is not niche:
`runtime.GOROOT()` **is** `gogetenv("GOROOT")`, which is what `internal/testenv.GOROOT` calls, which
is what any Go test needing the toolchain calls (`path/filepath`'s `TestBug3486` is the first
operational hit). A `[ModuleInitializer]` is the faithful stand-in for `schedinit`'s slot — it runs
when the runtime assembly is first touched, before any converted Go code in it, exactly once — and
fills `envs` from `Environment.GetEnvironmentVariables()` in Go's own `"key=value"` wire form, which
is what `gogetenv` scans (`'='` at exactly `len(key)`) and what `syscall.Environ` hands back. The
SNAPSHOT semantics are Go's, not a simplification: `GOROOT`'s doc says *"the GOROOT environment
variable, if set at process start"*, so a later `os.Setenv` neither does nor should appear here.
Note this is the only *read* path that was broken — on Windows `syscall.Environ`/`Getenv` go straight
to `GetEnvironmentStringsW` and never consulted `envs`.

⚠ **`defaultGOROOT` is a LINK-TIME constant and stays empty — a separate, open root.** `runtime.GOROOT()`
falls back to `defaultGOROOT` when the environment does not set `GOROOT`, and Go's is written by
`cmd/link` at build time (measured: with `GOROOT` unset, a real Go binary still answers
`runtime.GOROOT() == "C:\\Program Files\\Go"`). A converted assembly has no linker to write it, so it
answers `""` — exactly Go's own `-trimpath` case, which `testenv.findGOROOT` handles by walking up
from the working directory for `GOROOT/src/go.mod` and, failing that, **skipping** the test. So
`TestBug3486` now reaches its own decision instead of infrastructure-erroring, but skips where Go
passes. Fabricating a value at runtime (probing `PATH` for the Go installation) would invent behavior
Go does not have; the honest remedies are all at build time — e.g. having the converter carry its
`-goroot` into assembly metadata — and that is a ruling, not a lane fix. Left empty and named.

**`os.runtime_rand` — `core/os/tempfile_impl.cs`.** The throwing stub took out every `os.CreateTemp`
and `os.MkdirTemp` before either could pick a name, which is what made `io`'s `OffsetWriter` tests
infrastructure-error and what stopped `path/filepath`'s symlink tests from reaching `testenv`'s own
decision to skip them. What Go asks of `runtime.rand` *here* is stated by `tempfile.go` itself —
"a good chance the file doesn't exist yet - keeps the number of tries in TempFile to a minimum" — so
only two properties matter, and statistical quality is not one: the sequence must differ **across
processes** (a fixed seed reintroduces exactly the predictability Go moved away from) and it must be
callable from any goroutine (`CreateTemp` takes no lock). `Random.Shared` answers both — OS-seeded at
first use, every member documented thread-safe — and `NextBytes` fills the full 64 bits rather than
`NextInt64`'s 63, so the value is a faithful `uint64` even though `nextRandom` keeps only the low 32.
It is deliberately **not** a cryptographic source: security against a local attacker comes from
`O_EXCL`/`Mkdir` failing, exactly as in Go.

**Reach, measured** — an A/B over the two `*_impl.cs` files against one binary, with `path/filepath`
bucketed **per test** because its unrelated `FindFirstFile` root kills the host mid-suite and makes
every later verdict read `C#=""`:

| Package | Before | After | What moved |
|:--|:--|:--|:--|
| `io` | 47 / 54 | **51 / 54** | `TestOffsetWriter_Seek`, `TestOffsetWriter_WriteAt` and `TestWriteAt_PositionPriorToBase` (all three `infrastructure-error`: *"runtime_rand: … is not implemented"*), plus `TestOffsetWriter_Write` and its subtests (`fail`) |
| `path/filepath` | 34 / 55 | **47 / 55** | 13 tests, every one of them the same shape: `skip` ↔ `skip` |

`path/filepath`'s entire gain is `testenv.MustHaveSymlink`'s bucket turning into **skips that match
Go's skips** — `runtime_rand` sat under `MustHaveSymlink`'s `os.MkdirTemp` probe, so C# could not
reach the point where Go decides it lacks the symlink privilege. What remains there is two roots,
neither of them this one: **seven** tests reach the `FindFirstFile` struct-marshalling defect (one
hard `AccessViolation`, six `IndexOutOfRangeException` in `PinnedBuffer` — the same clobbered
`WIN32_FIND_DATAW`), and **one** is `TestBug3486` on `defaultGOROOT` above.

The `envs` half has its own **positive control**: export `GOROOT` into the host's environment and
`TestBug3486` *passes*, nothing else changed. That is what isolates the residual to the empty
link-time constant rather than to the snapshot — and it is the same probe to re-run if a future
build-time `GOROOT` remedy is tried.

## Comments

Comment conversion is opt-in (`-comments`, default **off**) and two consumers require it: the
standard-library conversion, because the converted C# is a derivative work whose per-file
`// Copyright … The Go Authors … BSD-style license` header must survive (and Go's doc comments are
most of what makes the output readable), and [Tour of go2cs](../src/tour/README.md), which converts
the lesson text a reader is looking at — there, a comment that lands in the wrong place is the most
visible defect the converter can have. (Behavioral goldens are captured *without* comments, so
comment placement never moves a golden; it is guarded by converter tests instead.)

### Where a comment is attached, and where it is not

Go's AST attaches a comment to a node only in specific places — a declaration's `Doc`, a struct
field's `Doc`/`Comment`, a spec's `Doc`/`Comment`. **Statements have no comment field at all.** So a
statement's comment is *free-floating*: it exists in `File.Comments` and nowhere else.

`visitFile` builds that set explicitly — every comment in `File.Comments`, keyed by its `//`
position, minus every comment group reachable by walking the AST (the attached ones). What is left
is `standAloneComments`, and it is flushed at the next emission point that asks for one
(`writeStandAloneCommentString`, reached from `writeDocString` and from the block-statement loop),
written on its own line with the block's indentation.

That is right for a comment that stood on its own line in Go. It is wrong for one that trailed a
statement, and the failure was not merely cosmetic: with the comment held until the *next* emission
point, the last statement of a block had no next statement to lead, so the comment was written after
the block had already closed — **outside the construct it documented**. Both Tour examples showed
it, one of each shape:

```go
for i := range pow {
	pow[i] = 1 << uint(i) // == 2**i
}
```

```csharp
// before
foreach (var (i, _) in pow) {
    pow[i] = ((nint)1).Lsh((nuint)i);
}
// == 2**i
```

```go
p := &i         // point to i
fmt.Println(*p) // read i through the pointer
```

```csharp
// before
var p = Ꮡi;
// point to i
fmt.Println(p.Value);
// read i through the pointer
```

### The rule

**A comment written on the same source line as a statement's END is emitted on the same line as that
statement's LAST emitted line.** Everything else keeps the standalone path unchanged.

```csharp
// after
foreach (var (i, _) in pow) {
    pow[i] = ((nint)1).Lsh((nuint)i); // == 2**i
}
```

```csharp
// after
var p = Ꮡi; // point to i
fmt.Println(p.Value); // read i through the pointer
```

A single space separates code from comment. The source column is deliberately *not* reproduced: Go's
alignment was computed for Go's line lengths, and the converted lines are a different length, so
replaying the original padding produces ragged output rather than a column. (An attached comment —
a struct field's — still uses the source-column padding in `writeCommentString`; that path is
untouched.)

Four details make the rule hold generally:

* **"Last emitted line", not "the statement's line".** One Go statement routinely lowers to several
  C# lines (a heap-boxed define, a hoisted capture snapshot, a lowered switch). The comment is
  written where the output builder actually stands after the statement is emitted, which is the end
  of that whole emission.
* **The statement-LIST slots are the only ones eligible** (`visitListStmt`): a block body, a `case`
  body, a `select` comm-clause body — the places where a statement's text is known to end its line.
  The init clause of an `if`/`for`/`switch` is deliberately excluded, because the rest of the header
  follows it on the same emitted line; a `//` comment tucked in there would comment the header out.
  Those callers keep calling `visitStmt` directly.
* **A statement that closes its own line is handled** — `visitSwitchStmt` ends its emission with a
  newline — by writing the comment *ahead* of that terminator rather than at column zero of the next
  line.
* **A multi-line block comment is not inlined.** A `/* … */` that opens on the statement's line but
  closes on a later one cannot be tucked onto a single line, so it stays with the standalone path,
  which can indent its continuation lines.

Because the comment is now claimed by the statement it belongs to, it can no longer be carried past
the closing brace: `} // after the if/else` and `} // after the switch` land on the brace, and a
block's final statement keeps its comment inside the block.

### What is still deferred

Three shapes remain genuinely leading and keep the standalone path — the comment stays where it
already was, which for the first two means *inside* the construct and for the third means after it:

* a comment after the `{` of a header (`for i := 0; i < n; i++ { // …`) leads the first body statement;
* a comment after a `case X:` label leads the case body — and, since a case body has no leading flush
  of its own, is still emitted at the next flush point past the switch;
* a whole-line comment standing before a block's closing brace with no statement after it.

Guarded by `trailingComments_test.go` — the two Tour examples plus the cross-statement-kind shapes,
asserting *positionally* (the comment shares its line with code that precedes it, and the block
closes after it) rather than by matching converted text, so an unrelated emission change does not
break the guard. The negative controls are in the same file: a whole-line comment and a multi-line
block comment must **not** be pulled onto a code line.

## Deterministic Output

Converter output is **byte-reproducible**: converting the same Go source with the same converter build produces byte-identical C# every run. This is a hard guarantee the goldens, the full-conversion error measurements, and any future release tag all rest on. Three mechanisms enforce it (all landed 2026-07-01, proven by two consecutive full-stdlib conversions diffing to zero across 305 packages):

* **Files convert sequentially, in sorted-filename order.** Per-file conversion previously ran in concurrent goroutines sharing package-level state claimed at visit time — `initΔN` module-initializer indices, blank-identifier temp numbering (`_ᴛN`, an unsynchronized map — a genuine data race), and imported collision-rename visibility: a file could mark an imported `package_info.cs` "parsed" *before* the parse finished, so a concurrently-converting file skipped the wait and emitted an imported renamed const **bare** (`abi.String` instead of `abi.ΔString` — a compile error that came and went with goroutine scheduling). Sequential conversion costs nothing measurable: a full-stdlib conversion is 3m42s concurrent vs 3m39s sequential (the cost is `go/packages` type-graph loading, not emission).
* **The stdlib conversion queue is deterministic and dependency-complete.** The topological sort now walks sorted roots (map-iteration roots made unrelated packages' order flip run-to-run), and a GOROOT-**vendored** dependency (imported as `golang.org/x/…` but keyed on disk as `vendor/golang.org/x/…`) is resolved to its vendored key — previously the edge was silently dropped, so an importer (e.g. `x/text/secure/bidirule`) could convert *before* its dependency (`x/text/unicode/bidi`), whose `package_info.cs` — the source of imported collision-rename aliases like `bidiꓸClass` — did not exist yet at that point.
* **Multi-box re-alias emission is sorted.** A multi-assignment that repoints several pointer boxes (`(Ꮡx, Ꮡy) = (Ꮡy, Ꮡx)`) emits its independent `n = ref Ꮡn.Value` refreshers in sorted name order (the set backing them is a map).

## Packages That Do Not Type-Check

A package the Go type checker could not fully resolve **still converts**, best-effort, and the run continues. This is not a tolerance the standard-library conversion needs — every stdlib package type-checks — but `-recurse` converts whatever an end-user module's closure reaches, and some of that legitimately does not resolve on the converting host: an app package naming a symbol that only exists behind a build tag, a cgo-only file, a third-party package whose own import failed. `go/packages` returns these WITH errors rather than failing the load.

The rules, in the order they matter:

* **Report, don't abort.** `processConversion` logs one `WARNING: <import path> did not fully type-check; converting best-effort` naming the package and listing the loader's errors, then converts. A load *failure* (the package could not be read at all) is different — that is returned as an error, and the batch drivers record the package as failed and move on.
* **An untyped expression is not a crash.** `go/types` records **no type at all** for an expression whose operand went invalid (`Checker.record` returns early for `mode == invalid`), so `types.Info.TypeOf` hands back a nil **interface**, not `Typ[Invalid]`. Every converter site that reaches a type through `TypeOf`/`getType` on an arbitrary source expression must therefore tolerate nil — `underlyingOf(t)` (`astTypeSyntax.go`) is the guarded `t.Underlying()` for exactly this, and it returns nil so the caller's `.(*types.X)` assertion simply reports not-ok and the fallback path runs. A type obtained from the type SYSTEM (a signature's parameter, a named type's RHS) is never nil and needs no wrapper.
* **The emitted C# for an untyped region cannot compile, and that is correct.** Nothing names the missing symbol, so the region emits as `invalid type u = default!` and similar. The surrounding declarations convert normally; the goal is that one unresolved symbol costs its own statements, not its file, not its package, and not the run.
* **A fault is contained per package.** `ModuleConverter.convertAll` and `StdLibConverter.convertPackage` each wrap a conversion in `recover`, so an unconvertible package fails alone. Any pass that spawns goroutines must re-raise a worker's panic on the caller's goroutine or that containment is silently void — a panic unwinds only its own goroutine and ends the process. `performEscapeAnalysis` captures the first worker panic **with its stack** (`debug.Stack()` before the frame is lost, so the report names the faulting converter line rather than the re-raise site) and re-panics after `Wait`. Issue #33 was exactly this hole: a nil-type dereference in the escape analysis killed a 1,726-package `-recurse` run at `[736/1726]`.

Guarded by `untypedPackageConversion_test.go` — one test converts a package mixing six invalid-operand shapes with healthy code and requires the healthy declarations to survive; the other injects a fault into the escape analysis and requires the caller to receive it.
<!-- {% endraw %} -->
