Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Verify pinned specification
run: grep -q "393b06f7c195e6040be74d4d170e4f930d2a8845" SPEC_BASELINE.md
run: grep -q "b1ed874e1e8eb0e61b2174b9093201e9a61e1a4b" SPEC_BASELINE.md
- name: Test deterministic core
run: swift test --skip CameraPlacementTests --skip CameraFairnessTests --parallel --num-workers 1
- name: Test camera placement
Expand All @@ -38,7 +38,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Verify pinned specification
run: grep -q "393b06f7c195e6040be74d4d170e4f930d2a8845" SPEC_BASELINE.md
run: grep -q "b1ed874e1e8eb0e61b2174b9093201e9a61e1a4b" SPEC_BASELINE.md
- name: Byte-compare bundled contracts and fixtures against the pinned SS-specs
run: bash scripts/spec_parity.sh

Expand Down
10 changes: 5 additions & 5 deletions App/WorldRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1121,9 +1121,10 @@ final class WorldRenderer {
}
mineNode.position = CGPoint(x: mine.x, y: mine.y)
if let shape = mineNode as? SKShapeNode {
shape.fillColor = mine.armed ? Palette.mineArmed : Palette.mineArming
shape.fillColor = .clear
shape.strokeColor = Palette.mineStroke
shape.lineWidth = mine.armed ? 3 : 1
// Weight carries arming versus armed. The path stays the radius.
shape.lineWidth = mine.armed ? 4 : 2
}
}
endLayer(.mines)
Expand Down Expand Up @@ -1576,9 +1577,8 @@ enum Palette {
static let playerProjectileTrail = SKColor(red: 1, green: 0.94, blue: 0.78, alpha: 0.35)
static let hostileProjectile = SKColor(red: 0.95, green: 0.45, blue: 0.30, alpha: 1)
static let hostileProjectileTrail = SKColor(red: 0.95, green: 0.45, blue: 0.30, alpha: 0.30)
static let mineArmed = SKColor(red: 0.85, green: 0.35, blue: 0.25, alpha: 0.35)
static let mineArming = SKColor(white: 0.6, alpha: 0.15)
static let mineStroke = SKColor(red: 0.9, green: 0.5, blue: 0.35, alpha: 0.8)
/// D-121. The ring is this stroke. White 0.92 at alpha 0.95. The fill is clear.
static let mineStroke = SKColor(white: 0.92, alpha: 0.95)
static let telegraphStroke = SKColor(red: 0.95, green: 0.55, blue: 0.35, alpha: 0.9)
/// D-094 gate barrier: the rail the warning lights sit on, the lights,
/// and the drawn fallback used only if the barricade art is missing.
Expand Down
74 changes: 74 additions & 0 deletions AppTests/MineRingPresentationTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import CoreGraphics
import SpriteKit
import Testing
@testable import SSRuntime
@testable import SurveillanceCore

/// T1060 / D-121. The Vendor mine is a stroke-only ring at the snapshot
/// radius. Arming is width 2. Armed is width 4. The fill stays clear.
@Suite(.serialized)
@MainActor
struct MineRingPresentationTests {
/// SpriteKit stores an assigned gray `SKColor` as float components in
/// extended sRGB. Compare at the thousandth, which is the precision of
/// white 0.92 and alpha 0.95.
private static func rgba(_ color: SKColor) -> (CGFloat, CGFloat, CGFloat, CGFloat) {
let space = CGColorSpace(name: CGColorSpace.extendedSRGB)!
let converted = color.cgColor.converted(to: space, intent: .defaultIntent, options: nil) ?? color.cgColor
let parts = converted.components ?? []
let thousandth = { (index: Int) -> CGFloat in
let value = index < parts.count ? parts[index] : -1
return (value * 1000).rounded() / 1000
}
return (thousandth(0), thousandth(1), thousandth(2), thousandth(3))
}

@Test func ringIsClearStrokeAtTheAuthoredRadius() throws {
let state = try Simulation.make(seed: 1).state
let mine = try #require(state.content.standardEnemies[.victorianVendor]?.mine)
var snap = PresentationSnapshot(state)
let arming = PresentationSnapshot.MineSprite(
id: EntityID(7),
x: 40,
y: 50,
radius: mine.radius,
armed: false,
armRemaining: mine.arm,
lifeRemaining: mine.lifetime
)
let armed = PresentationSnapshot.MineSprite(
id: EntityID(8),
x: 80,
y: 90,
radius: mine.radius,
armed: true,
armRemaining: 0,
lifeRemaining: mine.lifetime
)
snap.mines = [arming, armed]

let renderer = WorldRenderer()
renderer.render(snap)

let armingNode = try #require(renderer.root.childNode(withName: "//mine-7") as? SKShapeNode)
let armedNode = try #require(renderer.root.childNode(withName: "//mine-8") as? SKShapeNode)
let authored = SKShapeNode(circleOfRadius: CGFloat(mine.radius))
let authoredBounds = try #require(authored.path).boundingBox
let clear = (CGFloat(0), CGFloat(0), CGFloat(0), CGFloat(0))
let neutral = (CGFloat(0.92), CGFloat(0.92), CGFloat(0.92), CGFloat(0.95))
#expect(Self.rgba(Palette.mineStroke) == neutral)
for (sprite, node) in [(arming, armingNode), (armed, armedNode)] {
#expect(Self.rgba(node.fillColor) == clear)
#expect(Self.rgba(node.strokeColor) == neutral)
#expect(node.path?.boundingBox == authoredBounds)
#expect(node.position == CGPoint(x: sprite.x, y: sprite.y))
}
#expect(armingNode.lineWidth == 2)
#expect(armedNode.lineWidth == 4)

snap.mines = []
renderer.render(snap)
#expect(renderer.root.childNode(withName: "//mine-7") == nil)
#expect(renderer.root.childNode(withName: "//mine-8") == nil)
}
}
4 changes: 3 additions & 1 deletion SPEC_BASELINE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Status: PINNED
| Identity | Value |
|---|---|
| Specification repository | `scrimshawlife-ctrl/SS-specs` |
| Specification commit | `393b06f7c195e6040be74d4d170e4f930d2a8845` |
| Specification commit | `b1ed874e1e8eb0e61b2174b9093201e9a61e1a4b` |
| Ruleset | `ss-rules-014` |
| Content | `civic-seam-content-009` |
| Arena | `civic-seam-arena-004` |
Expand Down Expand Up @@ -86,3 +86,5 @@ D-118 adoption (SS-specs #87, ruleset `ss-rules-014`, content `civic-seam-conten
D-119 adoption (SS-specs #89): lethal warning, option 2 (T1058). A telegraph or a 60-tick contact horizon that would remove the current Integrity shows `LETHAL THREAT` ahead of Lockdown and Extraction. The card is presentation only: no lethal-warning event, no golden-stream change, and no ruleset, content, arena, or replay identity change. No admitted file matches the cue, so the carriers are the card and the `warning` haptic. The asset catalog's `specificationCommit` moves with this pin; its entries do not.

D-120 adoption (SS-specs #90): own the existing shell labels `START`, `SETTINGS`, and `RESTART` (T1059). No rename and no new control. Run-shell § 8.1 stays open. No contract or fixture change; pin move only. Ruleset, content, arena, and replay identities are unchanged.

D-121 adoption (SS-specs #91): the Victorian Vendor mine is a clear-filled ring (T1060). One light neutral stroke, white 0.92 at alpha 0.95, weight 2 while arming and 4 once armed. Arm, life, radius, damage, and the cap of two are unchanged. No caption and no asset id. No ruleset or content identity change. The asset catalog's `specificationCommit` moves with this pin; its entries do not.
2 changes: 1 addition & 1 deletion Sources/SurveillanceCore/ContractVersions.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
public enum ContractVersions {
public static let specificationCommit = "393b06f7c195e6040be74d4d170e4f930d2a8845"
public static let specificationCommit = "b1ed874e1e8eb0e61b2174b9093201e9a61e1a4b"
public static let ruleset = "ss-rules-014"
public static let content = "civic-seam-content-009"
public static let arena = "civic-seam-arena-004"
Expand Down
2 changes: 1 addition & 1 deletion Sources/SurveillanceCore/Resources/SPEC_PIN.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
SS-specs commit 393b06f7c195e6040be74d4d170e4f930d2a8845
SS-specs commit b1ed874e1e8eb0e61b2174b9093201e9a61e1a4b
ruleset ss-rules-014
content civic-seam-content-009
arena civic-seam-arena-004
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"schemaVersion": "asset-catalog-001",
"legacyRepository": "scrimshawlife-ctrl/Surveillance-Survivor",
"legacyCommit": "3b20d88d6a6e1fe8f07f45f581359d371fa65d98",
"specificationCommit": "393b06f7c195e6040be74d4d170e4f930d2a8845",
"specificationCommit": "b1ed874e1e8eb0e61b2174b9093201e9a61e1a4b",
"entries": [
{
"admissionDecision": "excluded",
Expand Down
Loading