- Anvil.Net.Query: new project — extracts AnvilNode tree, AnvilDoc, AnvilSchema, AnvilWriter, AnvilVarsState, scripting interfaces from Anvil.Net.Api (FR-2604-anvl-net-017)
- Anvil.Net.Scripting: new project — extracts AnvilScriptEngine, IAnvilScriptEngine, IAnvilScriptingPlugin from Anvil.Net.Api (FR-2604-anvl-net-018)
- Anvil.Net.Serializer: new project — extracts AnvilSerializer, AnvilCodeGen, IAnvilValueConverter, all Anvil*Attribute types and codegen from Anvil.Net.Api (FR-2604-anvl-net-019)
- Anvil.Net.Api: zero-source façade — references Query, Scripting, Serializer; no owned source files
- AnvilScriptingRegistry.TryAutoLoad: path-based LoadFrom probe with EnsureRegistered reflection call for reliable plugin activation in test hosts
- AslNodeKind.Ternary: ternary conditional expression cond ? then : else (BL-035-asl, FR-2604-anvl-net-014)
- AslParser.ParseExpr: ternary '?' check at minPrec==0 — lowest precedence, right-associative chaining
- AslEvaluator: Ternary branch evaluates cond.Truthy, short-circuits unevaluated branch
- TernaryTests: TN01–TN10 (true/false branches, int/string literals, expression branches, right-assoc chain, module argument)
- AslNodeKind.IndexAccess: index access collection[index] for List and Map values (BL-038-asl, FR-2604-anvl-net-015)
- AslParser.ParsePostfix: '[' branch chains after any primary expression — handles inline literal, variable, and computed index
- AslEvaluator.EvalIndexAccess: List bounds-checked (Null on miss), Map key coercion (string or long.ToString)
- IndexTests: IX01–IX10 (first/last/variable index, out-of-bounds, negative, string list, computed index, map bracket, split-result, inline literal)
- schema.enum: @[schema.enum] attribute on parenthesised declaration registers a named enumeration type in schema context (FR-2604-anvl-net-016)
- SchemaTypes.EnumTypeNode: new schema AST node (TypeDeclKind.Enum) with Name + Members list
- AnvilSchemaValidator: two-pass enum registry — pass 1 collects @[schema.enum] declarations, pass 2 resolves field type references against enum registry
- AnvilErrorCode.SchemaEnumValueMismatch = 4704: data field value is not a declared member of the named enum type
- SchemaTests TC-17 to TC-21: valid member passes, invalid raises 4704, unknown type name raises 4602, primitive regression, multi-enum no cross-contamination
- AslNodeKind.MapLiteral: map literal syntax { key: value, … } in expression position (BL-036-asl, FR-2604-anvl-net-011)
- AslParser.ParsePrimary: { key: expr } lookahead disambiguates map literal from block; empty {} supported
- AslEvaluator.EvalMapLiteral: builds Dictionary<string,AslValue> from alternating StringLiteral/expr child pairs
- AslNode.StringValue: string? field for pre-processed escape content; evaluator uses it in preference to span
- AslParser.ProcessEscapes: \n \t \r \" \\ processed at parse time in all string literals (BL-034-asl, FR-2604-anvl-net-012)
- AslParser.ParseInterpolatedString: $"…{ident}…" produces left-associative BinaryExpr(Add) chain seeded with empty StringLiteral (BL-039-asl, FR-2604-anvl-net-012)
- AnvilErrorCode.AslUnterminatedString = 4513: unterminated interpolated string or unclosed hole
- MapTests: ML01–ML08 (empty/single/multi-entry map literal, map as argument, dot access, nested access, missing key, truthiness)
- StringTests: ES01–ES05 (escape sequences) + IS01–IS06 (interpolated string: single/multi hole, no-hole, int coercion, local-from-$-ref, escape inside interpolation)
- AnvilAttrAttribute: POCO property → @[attribute] binding for serializer (FR-2604-anvl-net-010)
- [AnvilAttr(key)]: Maps property to block-level @[key=value] during deserialization/serialization
- AnvilSerializer.DeserializeObject: [AnvilAttr] binding loop calls node.Attribute(key) + CoerceValue
- AnvilSerializer.FormatAttributes: collects [AnvilAttr] properties, emits @[key="value", ...] before := operator
- AnvilSerializer.SerializeToWriter: attribute emission integrated for both inheritance chain and flat emit paths
- Test coverage: AnvilSerializerAttrTests.cs TC-1 through TC-10 (read/write paths, coercion, round-trip, escaping)
- AnvilSchema (.asch) — Phase 2 enforcement layer (FR-2604-anvl-net-013)
- Parser: @[schema] module attribute recognized; IsSchemaContext flag set; RHS bare-word tokens interpreted as type names in schema context
- SchemaTypes.cs: PrimitiveTypeNode, ArrayTypeNode, BlockTypeNode, TupleTypeNode, AnyTypeNode, SchemaField — schema AST (260 lines)
- AnvilSchemaValidator: document traversal, type matching, ValidateField, ValidateArrayElement, ValidatePathField, ValidateWithDefaults
- AnvilSchemaOptions: SchemaEnforcementMode (Strict | Advisory); Strict throws AnvilSchemaException, Advisory populates ValidationResult.Warnings
- AnvilSchemaResult: resolved document + Defaults dict + IsValid delegate; indexer injects default values for absent optional fields
- AnvilSchemaPathResolver: 4-step resolution — absolute → relative-to-file → schema.path search entries → polaris/schemas/ fallback
- Anvil.LoadWithSchema(dataPath, schemaPath, options): explicit schema binding for CI, runtime registry, offline validation
- AnvilErrorCode.SchemaTypeMismatch = 4701: data field value type does not match declared schema type
- AnvilErrorCode.SchemaPathInvalid = 4702: field @[schema.path] value is structurally invalid (empty, null bytes, or illegal path chars); no filesystem access
- AnvilErrorCode.SchemaArrayElementTypeMismatch = 4703: per-element array type validation
- Test coverage: SchemaTests TC-1 through TC-16 (schema context, Advisory/Strict modes, missing fields, table, path resolver, polaris fallback, defaults, tuples, type mismatch, array elements, nested blocks, any type, path structure, security boundary)
- docs/asch-authoring-guide.md: .asch file authoring guide with full type vocabulary, field attributes, path resolution, enforcement modes, and security properties
- EvalExpr switch: added MapLiteral case routing to EvalMapLiteral
- EvalExpr switch StringLiteral case: uses node.StringValue when non-null (escape-processed value)
- AslParser $ branch: $" routes to ParseInterpolatedString before VarRef fallback
- Parser: removed AttributesNotAllowedOnType restriction on top-level scalar statements — @[k=v] is now valid on any statement (required for .asch field attribute syntax)
- BL-040-asl seam hardening: removed ExternalLookup fallthrough from EvalIdent; bare identifiers now resolve from local ASL scope only — $ sigil required to cross ANVL/ASL boundary (SM01–SM04)
- IN02_05 SyntaxInquiryTests: updated to use $existing (was relying on removed bare-ident fallthrough)
- AnvilNode.Attributes: nested field attributes now exposed correctly via _field.AttribStart/AttribCount (was missing nested block attribute access)
- SchemaTypeMismatch (4701) rejects type confusion at the schema boundary — crafted strings cannot coerce to int/float/bool silently (TC-16: SQL injection analogue)
- SchemaArrayElementTypeMismatch (4703) validates every element independently — a bad element in position N cannot hide behind valid elements in 0..N-1
- SchemaPathInvalid (4702) validates path structure without File.Exists — no TOCTOU race; existence is audited at point of use by the caller
- Strict mode enforces hard type boundaries on document load — validation failure throws before data reaches application code
- any type is an explicit opt-out, not the default — every undeclared field requires deliberate author action to skip validation
- Advisory mode returns structured AnvilValidationError objects with machine-readable codes — warnings are inspectable, not silent
- AnvilBlobAttribute: Specialized AnvilMember attribute for blob properties with optional tag support (FR-2604-anvl-net-009)
- [AnvilBlob(key, tag)]: Inherits from AnvilMemberAttribute, adds Tag property for @png/@webp/@md blob syntax
- AnvilSerializer serialization: ToAnvil() now called during property serialization (completes round-trip)
- WritePropertyField(): Check for [AnvilConverter] before other attribute dispatch (blob tag wrapping)
- FormatPropEntry(): Check for [AnvilConverter] before other attribute dispatch (inline object path)
- Blob tag wrapping: @tag
base64...for tagged blobs,base64...for untagged blobs - Test coverage: ConverterTests.cs VC10-VC14 (5 new tests: tagged/untagged blob serialization, round-trip, key override)
- AnvilMemberAttribute: Changed from sealed class to public class (allows inheritance)
- AnvilSerializer: WritePropertyField() now checks for [AnvilConverter] and calls ToAnvil()
- AnvilSerializer: FormatPropEntry() now checks for [AnvilConverter] and calls ToAnvil()
- Users Guide: Added 'Blob Serialization with [AnvilBlob]' section (150+ lines with Vellum use case)
- FR-2604-anvl-net-009: IAnvilValueConverter.ToAnvil() now called during serialization
- byte[] with [AnvilConverter] now serializes as base64 string instead of 'System.Byte[]'
- Vellum FontAtlasDescriptor now supports full serialize + deserialize round-trip for 70KB PNG atlas blobs
- Serialization dispatch order: converter check now BEFORE array/tuple/map attribute checks
- IAnvilValueConverter: Zero-copy extensibility API for custom deserialization (FR-2604-anvl-net-008)
- IAnvilValueConverter.FromAnvil(ReadOnlySpan, Type): Zero-allocation decode from AML source
- IAnvilValueConverter.ToAnvil(object?, Type): Round-trip serialization support
- [AnvilConverter(typeof(T))]: Property-level attribute for converter dispatch
- AnvilSerializer.CoerceValue() integration: Converter dispatch before standard type coercion
- Test coverage: ConverterTests.cs with 9 passing tests (VC01-VC09: base64, DateTime, round-trip, nested objects, error handling)
- Users Guide: IAnvilValueConverter section with production-ready Base64Converter implementation
- Parser: Parse-time quote stripping for Scalar and Blob values (internal implementation change)
- ParseStatement(): Scalar values now use stripped position/length from ParseScalarValue()
- ParseCollection(): Array/Tuple elements use stripped positions for Scalar/Blob elements
- AnvilNode.AsString(): Unescape logic now only applies to Scalar type (blobs are raw content)
- XML doc comments: AnvilError.Current → Anvil.LastError (BL-002 API consistency)
- AnvilSchema.cs example code updated to use Anvil.LastError
- All public API documentation now references the correct Anvil.LastError property
- FR-2604-anvl-net-008: 70KB+ base64 blobs now avoid LOH (Large Object Heap) via zero-copy AsSpan()
- ParseStatement() position corruption: Scalar values no longer include surrounding quotes in metadata
- Array/Tuple element positions now correctly exclude quotes (parse-time stripping)
- Blob AsString() no longer incorrectly unescapes content (blobs are literal byte sequences)
- BL-002: Reconciled dual error access pattern documentation
- Removed incorrect references to non-existent AnvilError.Current property
- ModuleRegistryConcurrencyTests.cs: Thread-safety validation for AnvilModuleRegistry (BR-2604-anvl-net-002)
- Test coverage: MRC01-MRC06 (6 passing tests: concurrent Register/TryGet/Clear, Task-based parallelism, xUnit parallel pattern)
- BR-2604-anvl-net-004: AnvilNode.AsString() now returns unquoted string values
- AsString() automatically strips surrounding quotes from string literals
- AsString() unescapes standard escape sequences (\" \n \r \t \\)
- Removed redundant .Trim('"') calls from AnvilSerializer, AnvilScriptEngine, and AnvilNodeExtensions
- Users no longer need manual quote stripping — API returns semantic values
- ASL while loop: C-style while (cond) { body } control flow (BL-012)
- AslNodeKind.While: While loop node type for ASL (v1.3.0)
- AslParser: ParseWhileStatement() supporting while (expr) { stmts } syntax
- AslEvaluator: EvalWhile() with break/continue support
- Test coverage: WhileLoopTests.cs with 6 passing tests (WL01-WL06: simple, conditional, nested, mutation, break, continue)
- Anonymous block attribute syntax: identifier @[attrs] { fields } (v1.1.0 enhancement)
- Parser: LookaheadIsAnonObject() updated to detect optional @[...] attributes
- Parser: ParseAnonObject() now parses attributes before object value
- Test coverage: AB07-AB09 (anonymous block attributes, backward compatibility)
- Anvil.ParseToContext(source, dialect?): Parse once, return reusable context (FR-2604-004)
- Anvil.LoadToContext(path, dialect?): Load and parse file into reusable context
- Anvil.Resolve(context, options?): Resolve pre-parsed context with optional merge strategy
- AnvilContext.IsParsed flag: Prevents redundant parsing in context-reuse pattern
- AnvilContext.SourcePath property: Enables import resolution for file-based contexts
- Test coverage: ContextReuseTests.cs with 5 passing tests (CR01-CR05)
- AnvilException: Abstract base class for all Anvil-specific exceptions (BL-001)
- AnvilException constructors: (message), (message, inner) for consistent exception patterns
- Test coverage: ExceptionBaseTests.cs with 8 passing tests (EB01-EB08: hierarchy, catch patterns, properties)
- AslNodeKind.Break/Continue: Documentation updated to reflect for/while loop support
- Test AB06: Anonymous blocks now support optional attributes (backward compatible)
- AnvilResolverException: Now inherits from AnvilException (was Exception)
- AnvilVarsException: Now inherits from AnvilException (was Exception)
- AnvilValidationException: Now inherits from AnvilException (was Exception)
- BR-0409-002: AsString() now strips quotes and unescapes string literals
- BR-2604-003: Anonymous blocks with attributes no longer require explicit := operator
- AsString() returns "Player" instead of "\"Player\"" (ergonomics improvement)
- Removed .Trim('"') workaround from 35 test assertions across 8 files
- AnvilElementMeta: FieldStart and FieldCount properties for object-typed array elements (BR-2604-anvl-net-001)
- AnvilValue: ElemFieldStartsTemp and ElemFieldCountsTemp temporary arrays for parser metadata transfer
- Parser: Object field metadata capture in ParseCollection (arrays/tuples)
- Parser: Metadata transfer in ParseStatement for top-level array/tuple values
- AnvilNode: NodeFromField array/tuple metadata transfer (BR-2604-anvl-net-001 fix)
- AnvilNode: Integer indexer constructs valueMeta for object-typed elements (FR-2604-anvl-net-001)
- Test coverage: InlineObjectTests.cs with 15 passing tests (IN01, IN04-IN12, IN14-IN15, IN17-IN19)
- BR-2604-anvl-net-001: Parser now persists object field metadata in AnvilElementMeta
- Canonical array[i]['field'] access pattern now works for inline objects in arrays
- Lattice font glyph metrics use case (IN-2604-anvil-001) resolved
- IAnvilArrayView interface: lazy array view abstraction with Count property and GetElement() method
- ConcatenatedArrayView implementation: stores element metadata from base + derived arrays
- AnvilValue.VirtualArrayView property: attaches lazy views to merged values
- AnvilNode integration: indexer and NodeEnumerator check for VirtualArrayView before using Elements array
- Test coverage: AC01-AC04 (base+derived concat, predicate filtering, multiple elements, cross-source)
- MergeStrategies.ConcatArrays() now creates virtual array views (fully functional, resolves v1.1.0 limitation)
- AnvilNode.NodeFromField() passes field reference to enable VirtualArrayView access
- MergeStrategies.IsArray() null-safe check for empty array handling
- Anonymous block syntax (FR-2604-anvl-net-001): top-level immutable object declarations with bare identifier + brace
- AnvilStatementType.AnonymousObject enum value
- Parser: LookaheadIsAnonObject(), ParseAnonObject() methods
- Resolver validation: inheritance from anonymous blocks throws AnvilResolverException
- IMergeStrategy interface for custom inheritance field merging (FR-2604-anvl-net-002)
- DefaultMergeStrategy singleton for replacement semantics
- AnvilResolveOptions container for resolution configuration
- Anvil.Parse(source, options) and Anvil.Load(path, options) overloads
- MergeStrategies helper: ConcatArrays(), FilterBase(predicate), Compose(first, second)
- Public API exposure: AnvilField, AnvilValue (previously internal)
- Test coverage: AB01-AB06 (anonymous blocks), CM11 (inheritance rejection), MS01-MS05 (merge strategies)
- Packaging: Switched to standard dotnet pack NuGet toolchain
- NuGet metadata moved from nuspec + pack.py into Anvil.Net.Api.csproj
- dist/1.0.0-rc4/ package produced via dotnet pack
- Interpolated string slot syntax: {ref} (bare name) instead of {.varname} (dot-prefix) - BR_0326_001
- Parser now checks IsIdentifierStart immediately after { (no dot consumed)
- SchemaValidator: zero-alloc happy path (lazy List init only on first error)
- AslScope: inline 8-slot linear table (replaces Dictionary allocation per call frame)
- SB03 (warm validate) drops from 656 B to 0 B allocated
- AslEvaluator SE09: runtime error check precedes return check (AslCallDepthExceeded now surfaces correctly)
- 11 new ASL parse error codes (450x range): AslExpectedLBrace through AslAssignLhsNoName
- No-throw parsing policy enforced: AslParser and AslEvaluator no longer throw exceptions for flow control
- All 13 throw statements replaced with AnvilError.Set() + return null
- try/catch wrapper in Parse() removed
- Integration tests: IT01-IT05 (schema + validation + codegen pipeline end-to-end)
- README updated: badges, Current State section rewritten, API file table extended
- Docs pass: schema and code-gen usage snippets added
- dist/: all pre-rc1 snapshot folders removed (dist/1.0.0-rc1/ is sole artifact)
- AnvilCodeGen: POCO code generation from AnvilSchema
- CodeGenOptions: Namespace, NullableEnabled, UseRecords, Indent configuration
- CSharpEmitter: internal StringBuilder-based renderer (no Roslyn dependency)
- AnvilCodeGen.GenerateSource(AnvilSchema, options?) and GenerateSource(string, options?)
- Schema Object types map to sealed class (or sealed record)
- Schema Enum types map to C# enum
- Schema Flags types map to [Flags] enum with power-of-2 values
- Schema validation: .asch schema documents define named types (Object, Enum, Flags)
- AnvilSchema: Load(), Parse(), Types, Validate(), ValidateAll()
- .asch extension registered as AnvilDialect.Aml
- AnvilNode.TryGetOwn(string): scan own fields without following inheritance
- SchemaTypeKind enum: Object, Enum, Flags
- FieldRule, SchemaType, SchemaRuleset, AnvilSchema classes
- Error codes: 4601-4606 (SchemaAttrMissing through SchemaValidationUnknownField)
- Multi-error validation mode: Anvil.Validate*(path/source/paths) entry points
- AnvilValidationError struct: Code, Message, Line, Column, File
- AnvilValidationResult class: IsValid, Errors, ThrowIfInvalid()
- AnvilValidationException: thrown by ThrowIfInvalid(), carries full Result
- Anvil.Validate(path), ValidateSource(source), ValidateAll(paths)
- AMP scalar arrays and tuples: [scalar, ...] and (scalar, ...) now valid in #!amp
- Error code 4401 AmpArrayElementNotScalar: nesting forbidden in AMP
- AnvilWriter AMP array/tuple support (Object nodes still throw)
- @[ forbidden in AMP value positions (was already blocked at module/statement level)
- @[attr] on ASL function declarations: attributes preserved on function node
- AnvilScriptEngine.FunctionNode(name): returns full AnvilNode? for named function
- Host attribute-based discovery: FunctionNode(name)?.HasAttribute('hook')
- ParseFunctionDef now accepts attrMeta and stores directly (root cause fix)
- for (init; cond; step) {} loop with break and continue
- AslValueKind.List and AslValueKind.Map types
- Inline list literals: [a, b, c] in any expression position
- $key.field dotted-path access for AML object tree navigation
- Built-in modules (auto-registered): sys_math, sys_str, sys_io
- Error context: AnvilErrorState.FunctionName property
- C-style ASL syntax: assignment uses =, optional ;, i++/i--, +=/-=/etc, else if chains
- Flag-based control flow: return/break/continue use context flags (not exceptions)
- Local variable assignment: := → = (inside function bodies)
- Control flow eliminates ~177 exception allocations per fib(10) call
- Multi-file imports: import 'path' and import 'path' as alias
- Cross-source statement lookup: root['alias.Key']
- Cross-source VarRef resolution: .alias.varKey
- Cross-source inheritance: LocalType:import.BaseType
- AnvilImportSet: owns transitive import graph
- AnvilContext.ImportDecls, HasImports, AddImportDecl
- AnvilField.ExternalSourceData + CloneWithExternalSource
- Error codes: 4201-4207 (import errors)
- MergeBenchmarks: MB01-MB06 covering inheritance patterns
- O(1) statement hash index: lazy Dictionary<string,int> on root nodes
- NodeEnumerator struct: value-type enumerator for zero-alloc foreach
- Anvil.Net.Benchmarks project: BenchmarkDotNet 0.14.0 suite with 25 benchmarks
- docs/Benchmarks.md: version-tracked benchmark results
- TryGet(string) rewritten: direct hash path, no exception on miss
- TryGet miss latency: ~11,000 ns → ~20 ns (540× faster)
- TryGet miss allocation: 464 B → 0 B
- TryGet hit latency: ~300 ns → ~191 ns
- .gitignore cleanup: purged 163 tracked bin/obj artifacts
- [AnvilArray] attribute: maps POCO collection to Anvil array
- [AnvilMap] attribute: maps Dictionary<string,T> to Anvil object
- [AnvilTuple] / ValueTuple serialization support
- AnvilWriter minification: AnvilWriterOptions.Minify = true (26% allocation reduction)
- AnvilWriter dialect support: AMP enforcement, dialect-specific output
- String quoting: AnvilWriter quotes special chars correctly
- AnvilSerializer no longer over-eagerly quotes numeric strings
- Blob parser: AML/ASL backtick blobs now store AnvilValueType.Blob (was Scalar)
- AnvilWriter: serializes AnvilNode tree back to Anvil text
- AnvilSerializer.Serialize(obj): POCO to Anvil text conversion
- AnvilWriterOptions: configures pretty/minify, dialect, indentation, PreserveVars
- vars block: top-level vars { key := value ... } for document variables
- VarRef (.ref): bare .identifier resolved from vars pool
- Interpolated strings ($'...'): {.ref} slots resolved at AsString() time
- ReadOnlyMemory backing: zero-alloc span access via AsSpan()/AsMemory()
- Enum deserialization: AnvilSerializer supports C# enum properties
- AnvilVarsState circular-reference detection at construction time