< Summary

Information
Class: Trailblazer.Pathing.TraversalAuthoringMap
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Authoring/TraversalAuthoringMap.cs
Line coverage
96%
Covered lines: 90
Uncovered lines: 3
Coverable lines: 93
Total lines: 208
Line coverage: 96.7%
Branch coverage
89%
Covered branches: 52
Total branches: 58
Branch coverage: 89.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%8894.44%
Build()100%66100%
BuildChartCell(...)100%1616100%
ParseCell(...)92.85%282893.75%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Authoring/TraversalAuthoringMap.cs

#LineLine coverage
 1using FixedMathSharp;
 2using System;
 3
 4namespace Trailblazer.Pathing;
 5
 6/// <summary>
 7/// Builds chart data and explicit transitions from tokenized traversable-state authoring input.
 8/// </summary>
 9public sealed class TraversalAuthoringMap
 10{
 11
 12    /// <summary>
 13    /// Creates a new traversal authoring map with the specified parameters.
 14    /// </summary>
 15    /// <param name="chartName">The name of the chart.</param>
 16    /// <param name="sourceMap">The source map containing tokenized traversable-state data.</param>
 17    /// <param name="minBounds">The minimum bounds of the authored map in world space.</param>
 18    /// <param name="interval">The interval between voxels in the authored map.</param>
 19    /// <param name="legend">The legend used to interpret tokens in the source map.</param>
 20    /// <param name="transitionIdPrefix">The prefix applied to generated transition IDs.</param>
 21    /// <exception cref="ArgumentException"></exception>
 22    /// <exception cref="ArgumentNullException"></exception>
 4423    public TraversalAuthoringMap(
 4424        string chartName,
 4425        string[,,] sourceMap,
 4426        Vector3d minBounds,
 4427        Fixed64 interval,
 4428        TraversalLegend? legend = null,
 4429        string? transitionIdPrefix = null)
 30    {
 4431        if (string.IsNullOrWhiteSpace(chartName))
 032            throw new ArgumentException("Chart name cannot be null or whitespace.", nameof(chartName));
 33
 4434        ChartName = chartName;
 4435        SourceMap = sourceMap ?? throw new ArgumentNullException(nameof(sourceMap));
 4436        MinBounds = minBounds;
 4437        Interval = interval;
 4438        Legend = legend ?? TraversalLegend.CreateBuiltIn();
 4439        TransitionIdPrefix = string.IsNullOrWhiteSpace(transitionIdPrefix)
 4440            ? chartName
 4441            : transitionIdPrefix;
 4442    }
 43
 44    /// <summary>
 45    /// The chart name used for the built chart.
 46    /// </summary>
 47    public string ChartName { get; }
 48
 49    /// <summary>
 50    /// The raw token source map.
 51    /// </summary>
 52    public string[,,] SourceMap { get; }
 53
 54    /// <summary>
 55    /// The world-space minimum bounds of the authored map.
 56    /// </summary>
 57    public Vector3d MinBounds { get; }
 58
 59    /// <summary>
 60    /// The authored voxel interval.
 61    /// </summary>
 62    public Fixed64 Interval { get; }
 63
 64    /// <summary>
 65    /// The token legend used during parsing.
 66    /// </summary>
 67    public TraversalLegend Legend { get; }
 68
 69    /// <summary>
 70    /// Prefix applied to generated transition ids.
 71    /// </summary>
 72    public string TransitionIdPrefix { get; }
 73
 74    /// <summary>
 75    /// Parses the source map and builds a chart and set of explicit transitions according to the legend and authoring r
 76    /// </summary>
 77    /// <returns>A build result containing the generated chart and transitions.</returns>
 78    public TraversalBuildResult Build()
 79    {
 4380        int sizeY = SourceMap.GetLength(0);
 4381        int sizeX = SourceMap.GetLength(1);
 4382        int sizeZ = SourceMap.GetLength(2);
 83
 4384        var parsedCells = new ParsedTraversalCell[sizeY, sizeX, sizeZ];
 4385        var chartCells = new NavigationChartCell[sizeY, sizeX, sizeZ];
 86
 17087        for (int y = 0; y < sizeY; y++)
 27488            for (int x = 0; x < sizeX; x++)
 42489                for (int z = 0; z < sizeZ; z++)
 90                {
 12191                    ParsedTraversalCell parsedCell = ParseCell(SourceMap[y, x, z], y, x, z);
 11792                    parsedCells[y, x, z] = parsedCell;
 11793                    chartCells[y, x, z] = BuildChartCell(parsedCell);
 94                }
 95
 3996        var chart = NavigationChart.From3D(ChartName, chartCells, MinBounds, Interval);
 3997        TraversalTransition[] generatedTransitions =
 3998            GeneratedTraversalTransitionBuilder.BuildTransitions(chart, TransitionIdPrefix);
 3999        return new TraversalBuildResult(chart, generatedTransitions, TransitionIdPrefix);
 100    }
 101
 102    private static NavigationChartCell BuildChartCell(ParsedTraversalCell parsedCell)
 103    {
 117104        NavigationChartCell chartCell = parsedCell.Entry.ChartCell;
 117105        int costModifier = parsedCell.PathCostModifier;
 117106        NavigationChartCellFlags flags = chartCell.Flags;
 107
 117108        if (parsedCell.HasTransitionMarker
 117109            && (flags & NavigationChartCellFlags.ClimbSurfaceHint) != 0)
 110        {
 9111            flags |= NavigationChartCellFlags.ClimbTransitionHint;
 112        }
 113
 117114        if (!parsedCell.HasTransitionMarker || !parsedCell.CanGenerateTransition)
 115        {
 79116            if (costModifier == 0)
 117            {
 73118                if (flags == chartCell.Flags)
 66119                    return chartCell;
 120
 7121                return new NavigationChartCell(
 7122                    chartCell.TraversalKinds,
 7123                    chartCell.PathCostModifier,
 7124                    flags,
 7125                    chartCell.GeneratedTransitionMedia);
 126            }
 127
 6128            return new NavigationChartCell(
 6129                chartCell.TraversalKinds,
 6130                costModifier,
 6131                flags,
 6132                chartCell.GeneratedTransitionMedia);
 133        }
 134
 38135        if (chartCell.HasSolid)
 136        {
 22137            flags |= NavigationChartCellFlags.TransitionSourceHint
 22138                | NavigationChartCellFlags.TransitionDestinationHint;
 139        }
 140
 38141        return new NavigationChartCell(
 38142            chartCell.TraversalKinds,
 38143            costModifier != 0 ? costModifier : chartCell.PathCostModifier,
 38144            flags,
 38145            parsedCell.TransitionMedia);
 146    }
 147
 148    private ParsedTraversalCell ParseCell(string rawToken, int y, int x, int z)
 149    {
 121150        string normalizedToken = rawToken?.Trim() ?? string.Empty;
 121151        bool hasTransitionMarker = false;
 121152        if (normalizedToken.Length > 0)
 153        {
 93154            int markerIndex = normalizedToken.IndexOf('!');
 93155            if (markerIndex >= 0)
 156            {
 49157                if (markerIndex != normalizedToken.Length - 1
 49158                    || normalizedToken.LastIndexOf('!') != markerIndex)
 159                {
 2160                    throw new ArgumentException(
 2161                        $"Invalid token '{normalizedToken}' at [{y}, {x}, {z}]. Only a single trailing '!' marker is sup
 162                }
 163
 47164                hasTransitionMarker = true;
 47165                normalizedToken = normalizedToken[..^1].TrimEnd();
 47166                if (string.IsNullOrEmpty(normalizedToken))
 167                {
 1168                    throw new ArgumentException(
 1169                        $"Invalid token '{rawToken}' at [{y}, {x}, {z}]. Transition markers require a base token.");
 170                }
 171            }
 172        }
 173
 174        // Parse an optional inline path cost modifier suffix: <token>_<int> (e.g. "S_60", "SL_45").
 175        // The suffix is extracted after transition-marker stripping so "S_60!" is also valid.
 118176        int pathCostModifier = 0;
 118177        int underscoreIndex = normalizedToken.LastIndexOf('_');
 118178        if (underscoreIndex >= 0)
 179        {
 10180            string costPart = normalizedToken[(underscoreIndex + 1)..];
 10181            if (int.TryParse(costPart, out int parsedCost))
 182            {
 10183                pathCostModifier = parsedCost;
 10184                normalizedToken = normalizedToken[..underscoreIndex].TrimEnd();
 185            }
 186        }
 187
 118188        if (!Legend.TryGetEntry(normalizedToken, out TraversalLegendEntry entry))
 189        {
 0190            throw new ArgumentException(
 0191                $"Unknown traversable-state token '{rawToken}' at [{y}, {x}, {z}].");
 192        }
 193
 118194        if (hasTransitionMarker
 118195            && !entry.HasTransitionMedia
 118196            && (entry.ChartCell.Flags & NavigationChartCellFlags.ClimbSurfaceHint) == 0)
 197        {
 1198            throw new ArgumentException(
 1199                $"Token '{rawToken}' at [{y}, {x}, {z}] cannot be marked for transition generation.");
 200        }
 201
 202        // Ignore cost modifiers on skip cells; they contribute no traversal data.
 117203        if (!entry.ChartCell.HasTraversalData)
 34204            pathCostModifier = 0;
 205
 117206        return new ParsedTraversalCell(entry, hasTransitionMarker, pathCostModifier);
 207    }
 208}