com.wallstop-studios.unity-helpers 2.0.4 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/Docs/DATA_STRUCTURES.md +7 -7
  2. package/Docs/EFFECTS_SYSTEM.md +836 -8
  3. package/Docs/EFFECTS_SYSTEM_TUTORIAL.md +77 -18
  4. package/Docs/HULLS.md +2 -2
  5. package/Docs/ILIST_SORTING_PERFORMANCE.md +92 -0
  6. package/Docs/ILIST_SORTING_PERFORMANCE.md.meta +7 -0
  7. package/Docs/INDEX.md +10 -1
  8. package/Docs/Images/random_generators.svg +7 -7
  9. package/Docs/RANDOM_PERFORMANCE.md +18 -15
  10. package/Docs/REFLECTION_HELPERS.md +1 -1
  11. package/Docs/RELATIONAL_COMPONENTS.md +51 -6
  12. package/Docs/SERIALIZATION.md +1 -1
  13. package/Docs/SINGLETONS.md +2 -2
  14. package/Docs/SPATIAL_TREES_2D_GUIDE.md +3 -3
  15. package/Docs/SPATIAL_TREES_3D_GUIDE.md +3 -3
  16. package/Docs/SPATIAL_TREE_2D_PERFORMANCE.md +64 -64
  17. package/Docs/SPATIAL_TREE_3D_PERFORMANCE.md +64 -64
  18. package/Docs/SPATIAL_TREE_SEMANTICS.md +7 -7
  19. package/Editor/Core/Helper/AnimationEventHelpers.cs +1 -1
  20. package/Editor/CustomDrawers/WShowIfPropertyDrawer.cs +131 -41
  21. package/Editor/Utils/ScriptableObjectSingletonCreator.cs +175 -18
  22. package/README.md +42 -18
  23. package/Runtime/Core/Extension/IListExtensions.cs +720 -12
  24. package/Runtime/Core/Helper/UnityMainThreadDispatcher.cs +2 -3
  25. package/Runtime/Core/Random/AbstractRandom.cs +52 -5
  26. package/Runtime/Core/Random/DotNetRandom.cs +3 -3
  27. package/Runtime/Core/Random/FlurryBurstRandom.cs +285 -0
  28. package/Runtime/Core/Random/FlurryBurstRandom.cs.meta +3 -0
  29. package/Runtime/Core/Random/IllusionFlow.cs +3 -3
  30. package/Runtime/Core/Random/LinearCongruentialGenerator.cs +3 -3
  31. package/Runtime/Core/Random/PcgRandom.cs +6 -6
  32. package/Runtime/Core/Random/PhotonSpinRandom.cs +387 -0
  33. package/Runtime/Core/Random/PhotonSpinRandom.cs.meta +3 -0
  34. package/Runtime/Core/Random/RomuDuo.cs +3 -3
  35. package/Runtime/Core/Random/SplitMix64.cs +3 -3
  36. package/Runtime/Core/Random/SquirrelRandom.cs +6 -4
  37. package/Runtime/Core/Random/StormDropRandom.cs +271 -0
  38. package/Runtime/Core/Random/StormDropRandom.cs.meta +3 -0
  39. package/Runtime/Core/Random/UnityRandom.cs +3 -3
  40. package/Runtime/Core/Random/WyRandom.cs +6 -4
  41. package/Runtime/Core/Random/XorShiftRandom.cs +3 -3
  42. package/Runtime/Core/Random/XoroShiroRandom.cs +3 -3
  43. package/Runtime/Tags/Attribute.cs +144 -24
  44. package/Runtime/Tags/AttributeEffect.cs +119 -16
  45. package/Runtime/Tags/AttributeMetadataCache.cs +312 -3
  46. package/Runtime/Tags/AttributeModification.cs +59 -29
  47. package/Runtime/Tags/AttributesComponent.cs +20 -0
  48. package/Runtime/Tags/EffectBehavior.cs +171 -0
  49. package/Runtime/Tags/EffectBehavior.cs.meta +4 -0
  50. package/Runtime/Tags/EffectHandle.cs +5 -0
  51. package/Runtime/Tags/EffectHandler.cs +385 -39
  52. package/Runtime/Tags/EffectStackKey.cs +79 -0
  53. package/Runtime/Tags/EffectStackKey.cs.meta +4 -0
  54. package/Runtime/Tags/PeriodicEffectDefinition.cs +102 -0
  55. package/Runtime/Tags/PeriodicEffectDefinition.cs.meta +4 -0
  56. package/Runtime/Tags/PeriodicEffectRuntimeState.cs +40 -0
  57. package/Runtime/Tags/PeriodicEffectRuntimeState.cs.meta +4 -0
  58. package/Samples~/DI - Zenject/README.md +0 -2
  59. package/Tests/Editor/Attributes/WShowIfPropertyDrawerTests.cs +285 -0
  60. package/Tests/Editor/Attributes/WShowIfPropertyDrawerTests.cs.meta +11 -0
  61. package/Tests/Editor/Core/Attributes/RelationalComponentAssignerTests.cs +2 -2
  62. package/Tests/Editor/Tags/AttributeMetadataCacheTests.cs +192 -0
  63. package/Tests/Editor/Tags/AttributeMetadataCacheTests.cs.meta +11 -0
  64. package/{node_modules.meta → Tests/Editor/Tags.meta} +1 -1
  65. package/Tests/Editor/Utils/ScriptableObjectSingletonTests.cs +41 -0
  66. package/Tests/Runtime/Extensions/IListExtensionTests.cs +187 -1
  67. package/Tests/Runtime/Helper/ObjectsTests.cs +3 -3
  68. package/Tests/Runtime/Integrations/Reflex/RelationalComponentsReflexTests.cs +2 -2
  69. package/Tests/Runtime/Performance/IListSortingPerformanceTests.cs +346 -0
  70. package/Tests/Runtime/Performance/IListSortingPerformanceTests.cs.meta +11 -0
  71. package/Tests/Runtime/Performance/RandomPerformanceTests.cs +3 -0
  72. package/Tests/Runtime/Random/FlurryBurstRandomTests.cs +12 -0
  73. package/Tests/Runtime/Random/FlurryBurstRandomTests.cs.meta +3 -0
  74. package/Tests/Runtime/Random/PhotonSpinRandomTests.cs +12 -0
  75. package/Tests/Runtime/Random/PhotonSpinRandomTests.cs.meta +3 -0
  76. package/Tests/Runtime/Random/RandomProtoSerializationTests.cs +14 -0
  77. package/Tests/Runtime/Random/RandomTestBase.cs +39 -4
  78. package/Tests/Runtime/Random/StormDropRandomTests.cs +12 -0
  79. package/Tests/Runtime/Random/StormDropRandomTests.cs.meta +3 -0
  80. package/Tests/Runtime/Serialization/JsonSerializationTest.cs +4 -3
  81. package/Tests/Runtime/Serialization/ProtoInterfaceResolutionEdgeTests.cs +2 -2
  82. package/Tests/Runtime/Serialization/ProtoRootRegistrationTests.cs +1 -1
  83. package/Tests/Runtime/Serialization/ProtoSerializeBehaviorTests.cs +1 -1
  84. package/Tests/Runtime/Tags/AttributeEffectTests.cs +135 -0
  85. package/Tests/Runtime/Tags/AttributeEffectTests.cs.meta +3 -0
  86. package/Tests/Runtime/Tags/AttributeModificationTests.cs +137 -0
  87. package/Tests/Runtime/Tags/AttributeTests.cs +192 -0
  88. package/Tests/Runtime/Tags/AttributeTests.cs.meta +3 -0
  89. package/Tests/Runtime/Tags/EffectBehaviorTests.cs +184 -0
  90. package/Tests/Runtime/Tags/EffectBehaviorTests.cs.meta +3 -0
  91. package/Tests/Runtime/Tags/EffectHandlerTests.cs +618 -0
  92. package/Tests/Runtime/Tags/Helpers/RecordingEffectBehavior.cs +89 -0
  93. package/Tests/Runtime/Tags/Helpers/RecordingEffectBehavior.cs.meta +4 -0
  94. package/Tests/Runtime/Tags/PeriodicEffectDefinitionSerializationTests.cs +92 -0
  95. package/Tests/Runtime/Tags/PeriodicEffectDefinitionSerializationTests.cs.meta +3 -0
  96. package/package.json +1 -1
  97. package/scripts/lint-doc-links.ps1 +156 -11
  98. package/Tests/Runtime/Tags/AttributeDataTests.cs +0 -312
  99. /package/Tests/Runtime/Tags/{AttributeDataTests.cs.meta → AttributeModificationTests.cs.meta} +0 -0
@@ -0,0 +1,89 @@
1
+ namespace WallstopStudios.UnityHelpers.Tests.Tags.Helpers
2
+ {
3
+ using System.Collections.Generic;
4
+ using WallstopStudios.UnityHelpers.Tags;
5
+
6
+ public sealed class RecordingEffectBehavior : EffectBehavior
7
+ {
8
+ private static readonly HashSet<int> InstanceIds = new();
9
+
10
+ public static List<EffectBehaviorContext> ApplyContexts { get; } = new();
11
+
12
+ public static List<EffectBehaviorContext> TickContexts { get; } = new();
13
+
14
+ public static List<PeriodicInvocation> PeriodicInvocations { get; } = new();
15
+
16
+ public static List<EffectBehaviorContext> RemoveContexts { get; } = new();
17
+
18
+ public static int ApplyCount { get; private set; }
19
+
20
+ public static int TickCount { get; private set; }
21
+
22
+ public static int PeriodicTickCount { get; private set; }
23
+
24
+ public static int RemoveCount { get; private set; }
25
+
26
+ public static int InstanceCount => InstanceIds.Count;
27
+
28
+ public static void Reset()
29
+ {
30
+ ApplyCount = 0;
31
+ TickCount = 0;
32
+ PeriodicTickCount = 0;
33
+ RemoveCount = 0;
34
+ InstanceIds.Clear();
35
+ ApplyContexts.Clear();
36
+ TickContexts.Clear();
37
+ PeriodicInvocations.Clear();
38
+ RemoveContexts.Clear();
39
+ }
40
+
41
+ private void OnEnable()
42
+ {
43
+ _ = InstanceIds.Add(GetInstanceID());
44
+ }
45
+
46
+ public override void OnApply(EffectBehaviorContext context)
47
+ {
48
+ ++ApplyCount;
49
+ ApplyContexts.Add(context);
50
+ }
51
+
52
+ public override void OnTick(EffectBehaviorContext context)
53
+ {
54
+ ++TickCount;
55
+ TickContexts.Add(context);
56
+ }
57
+
58
+ public override void OnPeriodicTick(
59
+ EffectBehaviorContext context,
60
+ PeriodicEffectTickContext tickContext
61
+ )
62
+ {
63
+ ++PeriodicTickCount;
64
+ PeriodicInvocations.Add(new PeriodicInvocation(context, tickContext));
65
+ }
66
+
67
+ public override void OnRemove(EffectBehaviorContext context)
68
+ {
69
+ ++RemoveCount;
70
+ RemoveContexts.Add(context);
71
+ }
72
+
73
+ public readonly struct PeriodicInvocation
74
+ {
75
+ public PeriodicInvocation(
76
+ EffectBehaviorContext context,
77
+ PeriodicEffectTickContext tickContext
78
+ )
79
+ {
80
+ Context = context;
81
+ TickContext = tickContext;
82
+ }
83
+
84
+ public EffectBehaviorContext Context { get; }
85
+
86
+ public PeriodicEffectTickContext TickContext { get; }
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,4 @@
1
+ fileFormatVersion: 2
2
+ guid: a6f0b0f43f73471ebadf08c3cfd5d72b
3
+ timeCreated: 1759598400
4
+
@@ -0,0 +1,92 @@
1
+ namespace WallstopStudios.UnityHelpers.Tests.Tags
2
+ {
3
+ using System.Collections.Generic;
4
+ using NUnit.Framework;
5
+ using WallstopStudios.UnityHelpers.Tags;
6
+ using Serializer = WallstopStudios.UnityHelpers.Core.Serialization.Serializer;
7
+
8
+ [TestFixture]
9
+ public sealed class PeriodicEffectDefinitionSerializationTests
10
+ {
11
+ [Test]
12
+ public void JsonRoundtripPreservesFieldValues()
13
+ {
14
+ PeriodicEffectDefinition definition = CreateDefinition();
15
+
16
+ string json = Serializer.JsonStringify(definition);
17
+ PeriodicEffectDefinition deserialized =
18
+ Serializer.JsonDeserialize<PeriodicEffectDefinition>(json);
19
+
20
+ AssertEquivalent(definition, deserialized);
21
+ }
22
+
23
+ [Test]
24
+ public void ProtoRoundtripPreservesFieldValues()
25
+ {
26
+ PeriodicEffectDefinition definition = CreateDefinition();
27
+
28
+ byte[] serialized = Serializer.ProtoSerialize(definition);
29
+ Assert.IsNotNull(serialized);
30
+ Assert.Greater(serialized.Length, 0);
31
+
32
+ PeriodicEffectDefinition deserialized =
33
+ Serializer.ProtoDeserialize<PeriodicEffectDefinition>(serialized);
34
+
35
+ AssertEquivalent(definition, deserialized);
36
+ }
37
+
38
+ private static PeriodicEffectDefinition CreateDefinition()
39
+ {
40
+ PeriodicEffectDefinition definition = new()
41
+ {
42
+ name = "Damage Pulse",
43
+ initialDelay = 0.35f,
44
+ interval = 0.8f,
45
+ maxTicks = 6,
46
+ modifications = new List<AttributeModification>
47
+ {
48
+ new()
49
+ {
50
+ attribute = "health",
51
+ action = ModificationAction.Addition,
52
+ value = -7.5f,
53
+ },
54
+ new()
55
+ {
56
+ attribute = "armor",
57
+ action = ModificationAction.Multiplication,
58
+ value = 0.85f,
59
+ },
60
+ },
61
+ };
62
+
63
+ return definition;
64
+ }
65
+
66
+ private static void AssertEquivalent(
67
+ PeriodicEffectDefinition expected,
68
+ PeriodicEffectDefinition actual
69
+ )
70
+ {
71
+ Assert.IsNotNull(actual);
72
+ Assert.AreNotSame(expected, actual);
73
+ Assert.AreEqual(expected.name, actual.name);
74
+ Assert.AreEqual(expected.initialDelay, actual.initialDelay);
75
+ Assert.AreEqual(expected.interval, actual.interval);
76
+ Assert.AreEqual(expected.maxTicks, actual.maxTicks);
77
+
78
+ Assert.IsNotNull(actual.modifications);
79
+ Assert.AreNotSame(expected.modifications, actual.modifications);
80
+ Assert.AreEqual(expected.modifications.Count, actual.modifications.Count);
81
+
82
+ for (int i = 0; i < expected.modifications.Count; ++i)
83
+ {
84
+ AttributeModification expectedModification = expected.modifications[i];
85
+ AttributeModification actualModification = actual.modifications[i];
86
+ Assert.AreEqual(expectedModification.attribute, actualModification.attribute);
87
+ Assert.AreEqual(expectedModification.action, actualModification.action);
88
+ Assert.AreEqual(expectedModification.value, actualModification.value);
89
+ }
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 1941617e59df425a8e8f76c34af56789
3
+ timeCreated: 1761255628
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.wallstop-studios.unity-helpers",
3
- "version": "2.0.4",
3
+ "version": "2.1.1",
4
4
  "displayName": "Unity Helpers",
5
5
  "description": "Various Unity Helper Library",
6
6
  "dependencies": {},
@@ -3,6 +3,7 @@ Param(
3
3
  )
4
4
 
5
5
  $ErrorActionPreference = 'Stop'
6
+ $repoRoot = (Resolve-Path -LiteralPath '.').Path
6
7
 
7
8
  function Write-Violation {
8
9
  param(
@@ -19,11 +20,110 @@ function Write-Violation {
19
20
  }
20
21
  }
21
22
 
23
+ $schemePattern = [regex]'^[a-zA-Z][a-zA-Z0-9+\.-]*:'
24
+
25
+ function Get-MarkdownLinkTarget {
26
+ param([string]$RawTarget)
27
+
28
+ if ([string]::IsNullOrWhiteSpace($RawTarget)) {
29
+ return $null
30
+ }
31
+
32
+ $trimmed = $RawTarget.Trim()
33
+
34
+ if ($trimmed.StartsWith('<') -and $trimmed.Contains('>')) {
35
+ $closingIndex = $trimmed.IndexOf('>')
36
+ if ($closingIndex -gt 1) {
37
+ $trimmed = $trimmed.Substring(1, $closingIndex - 1)
38
+ }
39
+ }
40
+
41
+ $spaceIndex = $trimmed.IndexOfAny(@(' ', "`t"))
42
+ if ($spaceIndex -ge 0) {
43
+ $trimmed = $trimmed.Substring(0, $spaceIndex)
44
+ }
45
+
46
+ if ([string]::IsNullOrWhiteSpace($trimmed)) {
47
+ return $null
48
+ }
49
+
50
+ return $trimmed
51
+ }
52
+
53
+ function Is-LocalImageTarget {
54
+ param([string]$Target)
55
+
56
+ if ([string]::IsNullOrWhiteSpace($Target)) {
57
+ return $false
58
+ }
59
+
60
+ $trimmed = $Target.Trim()
61
+ if ($schemePattern.IsMatch($trimmed)) {
62
+ return $false
63
+ }
64
+
65
+ if ($trimmed.StartsWith('#') -or $trimmed.StartsWith('//')) {
66
+ return $false
67
+ }
68
+
69
+ return $true
70
+ }
71
+
72
+ function Resolve-ImagePath {
73
+ param(
74
+ [string]$SourceFile,
75
+ [string]$Target,
76
+ [string]$RepoRoot
77
+ )
78
+
79
+ if ([string]::IsNullOrWhiteSpace($Target)) {
80
+ return $null
81
+ }
82
+
83
+ $normalized = $Target.Trim() -replace '\\', '/'
84
+ $cutIndex = $normalized.IndexOfAny(@('#', '?'))
85
+ if ($cutIndex -ge 0) {
86
+ $normalized = $normalized.Substring(0, $cutIndex)
87
+ }
88
+
89
+ while ($normalized.StartsWith('./')) {
90
+ $normalized = $normalized.Substring(2)
91
+ }
92
+
93
+ $normalized = $normalized.Trim()
94
+ if ([string]::IsNullOrWhiteSpace($normalized)) {
95
+ return $null
96
+ }
97
+
98
+ $separator = [System.IO.Path]::DirectorySeparatorChar
99
+ $normalized = $normalized.Replace('/', $separator).Replace('\', $separator)
100
+
101
+ $sourceDirectory = Split-Path -Path $SourceFile -Parent
102
+ $candidate = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($sourceDirectory, $normalized))
103
+
104
+ if (Test-Path -LiteralPath $candidate) {
105
+ return $candidate
106
+ }
107
+
108
+ if ($normalized.Length -gt 0 -and $normalized[0] -ne '.') {
109
+ $rootRelative = $normalized.TrimStart($separator)
110
+ $candidateFromRoot = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($RepoRoot, $rootRelative))
111
+ if (Test-Path -LiteralPath $candidateFromRoot) {
112
+ return $candidateFromRoot
113
+ }
114
+ }
115
+
116
+ return $null
117
+ }
118
+
22
119
  $mdPattern = [regex]'[A-Za-z0-9._/\-]+\.md(?:#[A-Za-z0-9_\-]+)?'
23
120
  $linkPattern = [regex]'\[[^\]]+\]\([^)]+\)'
24
121
  $anglePattern = [regex]'<[^>]+>'
25
122
  $filenameTextLinkPattern = [regex]'\[(?<text>[^\]]+?\.md(?:#[^\]]+)?)\]\((?<target>[^)]+?\.md(?:#[^)]+)?)\)'
26
123
  $inlineCodeMdPattern = [regex]'`[^`\n]*?\.md[^`\n]*?`'
124
+ $imagePattern = [regex]'!\[[^\]]*\]\((?<target>[^)]+)\)'
125
+ $imageReferencePattern = [regex]'!\[[^\]]*\]\[(?<label>[^\]]+)\]'
126
+ $definitionPattern = [regex]'^\s*\[(?<label>[^\]]+)\]:\s*(?<rest>.+)$'
27
127
 
28
128
  $violationCount = 0
29
129
 
@@ -31,44 +131,88 @@ Get-ChildItem -Path . -Recurse -Include *.md -File |
31
131
  Where-Object { $_.FullName -notmatch '(?i)[\\/](node_modules)[\\/]' } |
32
132
  ForEach-Object {
33
133
  $file = $_.FullName
134
+ $lines = Get-Content -LiteralPath $file
135
+ $lineCount = $lines.Length
136
+ $linkDefinitions = New-Object 'System.Collections.Generic.Dictionary[string,string]' ([System.StringComparer]::OrdinalIgnoreCase)
137
+ $inFence = $false
138
+
139
+ for ($index = 0; $index -lt $lineCount; $index++) {
140
+ $line = $lines[$index]
141
+ if ($line -match '^\s*```' -or $line -match '^\s*~~~') {
142
+ $inFence = -not $inFence
143
+ continue
144
+ }
145
+ if ($inFence) { continue }
146
+
147
+ $definitionMatch = $definitionPattern.Match($line)
148
+ if ($definitionMatch.Success) {
149
+ $label = $definitionMatch.Groups['label'].Value.Trim()
150
+ $rest = $definitionMatch.Groups['rest'].Value
151
+ $target = Get-MarkdownLinkTarget $rest
152
+ if ($label -and $target) {
153
+ $linkDefinitions[$label] = $target
154
+ }
155
+ }
156
+ }
157
+
34
158
  $inFence = $false
35
- $lineNo = 0
36
- Get-Content -LiteralPath $file | ForEach-Object {
37
- $line = $_
38
- $lineNo++
159
+ for ($index = 0; $index -lt $lineCount; $index++) {
160
+ $line = $lines[$index]
161
+ $lineNo = $index + 1
39
162
 
40
- # Track fenced code blocks (``` or ~~~)
41
163
  if ($line -match '^\s*```' -or $line -match '^\s*~~~') {
42
164
  $inFence = -not $inFence
165
+ continue
43
166
  }
44
- if ($inFence) { return }
167
+ if ($inFence) { continue }
45
168
 
46
- # 1) Flag inline code mentions of .md
47
169
  if ($inlineCodeMdPattern.IsMatch($line)) {
48
170
  $violationCount++
49
171
  Write-Violation -File $file -LineNumber $lineNo -Message "Inline code mentions .md; use a human-readable link instead" -Line $line
50
172
  }
51
173
 
52
- # Strip markdown links and angle-bracket autolinks before scanning for bare mentions
53
174
  $stripped = $linkPattern.Replace($line, '')
54
175
  $stripped = $anglePattern.Replace($stripped, '')
55
176
 
56
- # 2) Bare .md mention outside links
57
177
  if ($mdPattern.IsMatch($stripped)) {
58
178
  $violationCount++
59
179
  Write-Violation -File $file -LineNumber $lineNo -Message "Bare .md mention; convert to [Readable Text](file.md)" -Line $line
60
180
  }
61
181
 
62
- # 3) Links whose visible text is still a filename
63
182
  foreach ($m in $filenameTextLinkPattern.Matches($line)) {
64
183
  $text = $m.Groups['text'].Value
65
184
  $target = $m.Groups['target'].Value
66
- # Ignore if the text already contains spaces (unlikely for filename.md); otherwise flag
67
185
  if ($text -match '\.md') {
68
186
  $violationCount++
69
187
  Write-Violation -File $file -LineNumber $lineNo -Message "Link text is a filename; use human-readable text for $target" -Line $line
70
188
  }
71
189
  }
190
+
191
+ foreach ($match in $imagePattern.Matches($line)) {
192
+ $rawTarget = $match.Groups['target'].Value
193
+ $resolvedTarget = Get-MarkdownLinkTarget $rawTarget
194
+ if ($resolvedTarget -and (Is-LocalImageTarget $resolvedTarget)) {
195
+ $imagePath = Resolve-ImagePath -SourceFile $file -Target $resolvedTarget -RepoRoot $repoRoot
196
+ if (-not $imagePath) {
197
+ $violationCount++
198
+ Write-Violation -File $file -LineNumber $lineNo -Message "Image target '$resolvedTarget' does not resolve to a file" -Line $line
199
+ }
200
+ }
201
+ }
202
+
203
+ foreach ($match in $imageReferencePattern.Matches($line)) {
204
+ $label = $match.Groups['label'].Value.Trim()
205
+ if ($linkDefinitions.ContainsKey($label)) {
206
+ $resolvedTarget = $linkDefinitions[$label]
207
+ if ($resolvedTarget -and (Is-LocalImageTarget $resolvedTarget)) {
208
+ $imagePath = Resolve-ImagePath -SourceFile $file -Target $resolvedTarget -RepoRoot $repoRoot
209
+ if (-not $imagePath) {
210
+ $violationCount++
211
+ Write-Violation -File $file -LineNumber $lineNo -Message "Image reference '$label' points to '$resolvedTarget' which does not resolve" -Line $line
212
+ }
213
+ }
214
+ }
215
+ }
72
216
  }
73
217
  }
74
218
 
@@ -78,3 +222,4 @@ if ($violationCount -gt 0) {
78
222
  } else {
79
223
  Write-Host "Markdown link lint passed: no issues found." -ForegroundColor Green
80
224
  }
225
+