Commit graph

2,087 commits

Author SHA1 Message Date
Meta Vens
2c2b92579f Map build delivery, and narrow the Idunn invariant to services
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:00:45 +02:00
Meta Vens
6dc1860770 Target a public build download served from Yggdrasil
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 21:42:17 +02:00
Meta Vens
1428ada3cd Repair the Addressables map after a bad sed prefixed every line
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 19:33:50 +02:00
Meta Vens
78fd617cdd Restore the escaped dot in the corrected grep
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 19:33:24 +02:00
Meta Vens
3744ee16da Fix a control character in the corrected grep
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 19:33:06 +02:00
Meta Vens
45406df974 Rule sub-asset references as Addressables guid[name] keys
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 19:32:53 +02:00
Meta Vens
dcf1881db7 Rule Addressables Q1 and sweep the settings map onto asset GUIDs
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 18:36:59 +02:00
Meta Vens
ec3be4f4b5 Cut 0: mutation-test the entity-settings-copy rule
Adds a control (no-op) and a red mutation reverting Entity's constructor
to alias its settings argument instead of copying it. Both run against
EntitiesDoNotShareDefaultEntitySettings; the red mutation is killed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 18:32:45 +02:00
Meta Vens
b6a27e733d Note stale CultLib worktrees carrying the path attribute
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 18:26:48 +02:00
Meta Vens
45941f597d Map engine asset references onto Addressables GUIDs
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 18:26:26 +02:00
Meta Vens
f839631334 Cut 0: MainMenu takes a run-owned copy of the background settings
MainMenu.cs wrote NoisePosition directly onto the shared authored
SectorBackgroundSettings/TutorialBackgroundSettings objects on every New
Game. Once settings are a catalog global that write lands on the cached
record. Take a MessagePack round-trip copy before searching and seeding
Galaxy; the authored object is never assigned (settings-globals-cut.md 1.4,
Cut 0).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 18:22:41 +02:00
Meta Vens
0c2da217f3 Cut 0: give Entity its own EntitySettings copy, delete the duplicate copies
Entity's constructor now takes a MessagePack round-trip copy of the settings
it is given, so no entity can alias GameplaySettings.DefaultEntitySettings.
That makes ActionGameManager.NewEntitySettings and EntitySerializer.Unpack's
null-coalescing copy redundant; both are deleted, and TradeMenu buys a ship
straight off the template (Entity's ctor copies it).

EntitiesDoNotShareDefaultEntitySettings pins the invariant: writing
ShutdownPerformance on one ship must not move the default for a sibling or
the template (settings-globals-cut.md, Cut 0).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 18:22:22 +02:00
Meta Vens
52011771bd Cut 0: delete dead soundbank and HullHitColor fields (fork X)
DefaultOverworldSoundbank/DefaultCombatSoundbank/DefaultBossSoundbank/
AmbienceSoundBank and HullHitColor have no reader in Assets/Scripts
(settings-globals-cut.md 1.1).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 18:22:00 +02:00
Meta Vens
ea8b420e60 Cut 0: delete the 5 dead settings assets (fork X)
Atmosphere Settings, Volume Settings, Elysium, Test Galaxy and Icons hold
data for scripts deleted in 2021-2022. Nothing under Assets/Scripts, tools
or tests reads them (settings-globals-cut.md 1.2).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 18:21:24 +02:00
Meta Vens
f13a3356e5 Rule settings-map forks: Addressables GUIDs for engine assets, defaults for the rest
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 18:09:42 +02:00
Meta Vens
0c62abb0ea Record the passed play smoke and the master merge
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 18:02:24 +02:00
Meta Vens
38eb9ca50d Record that the content pass consolidates designs through roles
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 17:49:14 +02:00
Meta Vens
826dae9c4d Record that product names are brands and design names are functional
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 17:46:31 +02:00
Meta Vens
6587363e2e Record the design-text and product-text ruling
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 17:44:43 +02:00
Meta Vens
db420981da Map settings out of Unity serialization into CultCache globals
Operator ruling: all settings live in CultCache globals. Unity-serialized
CultMath fields load as zero, which cost mouse sensitivity and the rarity
tier colours. Marks the playground map's fork S as superseded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 17:36:07 +02:00
Meta Vens
00ebe46abe Move mouse-look sensitivity into the PlayerSettings CultCache global
ActionGameManager.Sensitivity was a Unity-serialized public float2, but
CultMath's float2 isn't Unity-serializable, so ARPG.unity's stored value
loaded as zero and mouse look went dead. Sensitivity is a player
preference, so it belongs in PlayerInputSettings (key 2) with the prior
default (-0.001, 0.001) as a field initializer, so a pre-existing player
store missing the key still deserializes to the working default instead
of zero. ActionGameManager now reads PlayerSettings.InputSettings.Sensitivity
directly; no cached copy on the MonoBehaviour. Adds a pinning test that
simulates an old store lacking key 2 and asserts the default, plus a
round-trip test for a non-default value.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 17:22:09 +02:00
Meta Vens
cf60fd0707 Assume a pausing in-game dialog primitive in the tutorial sketch
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 15:21:12 +02:00
Meta Vens
b450361431 Sketch a tutorial script driven through existing agent primitives
AI-extended material built on Emily Harvey's 2020 tutorial premise (Threefra,
the staged assessment); her text is not reproduced. Beats advance on events and
mark which primitives exist, which are data-only, and which are missing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 15:20:56 +02:00
Meta Vens
a3c8d55d83 Record kinematic and rail bodies sharing one detection interface
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:50:20 +02:00
Meta Vens
d1972de0de Record that simulation owns the grabbed loot object and its pose
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:44:13 +02:00
Meta Vens
88bcbc94b0 Test that capability events route only to their own kind's subscribers
CapabilityEventsTests.EachEventKindReachesOnlyItsOwnSubscribers publishes one
of each event kind and asserts every subscriber list holds exactly its own
event -- a subscriber to Absorb must never see a Grab/Melee/Thrust event.
Mutation entry in tests/mutation_tests.py kills a mutant that makes
PublishAbsorb also notify the Thrust stream, plus a no-op control proving the
mutation script's own read/replace/restore path is transparent for this file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:37:07 +02:00
Meta Vens
807c4fab7e Drive the field shield test scene by publishing capability events
FieldTester now owns a CapabilityEvents instance and stands in for the
capability source the simulation doesn't have yet: it publishes AbsorbEvent
on click (cycling DamageType so the operator can see how a type-aware
presenter would behave), GrabEvent(Started) on pickup click, MeleeEvent on
the Melee button, and a ThrustEvent every frame from input -- FieldDriver only
ever reacts through CapabilityPresentationBinder. The damped push/twist state
that used to live directly on FieldDriver.Push/FrontTwist/RearTwist now lives
in FieldTester and is applied through the event instead.

Adds a CapabilityPresentationBinder component to the field object in
FieldShieldTest.unity, alongside the existing added ClickableCollider
component on that same prefab-instance GameObject (same pattern, no
serialized fields to wire). Scene behavior is unchanged: hits, melee, pickups
and flow all look and act as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:36:54 +02:00
Meta Vens
2138aa4560 Make FieldDriver a capability presenter, not an owner
FieldDriver now implements IAbsorbPresenter/IGrabPresenter/IMeleePresenter/
IThrustPresenter by delegating to its existing AddHit/GrabObject/Melee/Push
fields; it still decides nothing about capability rules, only how they render
(fork L, docs/headless-playground-cut.md). Its own click-to-hit wiring moves
out to whatever publishes AbsorbEvent (the test scene, next commit) since a
presenter should not be sourcing its own capability events.

The private nested GrabPhase enum is renamed to TendrilPhase so it doesn't
collide with the new global GrabPhase in CapabilityEvents.cs; this is the only
behavior-preserving rename, tendril timing is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:36:40 +02:00
Meta Vens
450d0731f7 Add presenter interfaces and a capability event binder
IAbsorbPresenter/IGrabPresenter/IMeleePresenter/IThrustPresenter let a Unity
component declare which capability events it visualizes without knowing about
CapabilityEvents itself. CapabilityPresentationBinder is the only thing that
subscribes a presenter to its matching stream, resolving a GrabEvent's plain
target handle to a live Transform (and initial velocity) through delegates its
owner supplies, since the engine-free GrabEvent can't carry a Unity reference.
Neither file knows about FieldDriver or any other concrete presentation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:36:27 +02:00
Meta Vens
fdea06f27f Add an engine-free capability event contract
Fork L (docs/headless-playground-cut.md): pickup, impact absorption, melee
blade and propulsion are separate item capabilities that a future simulation
will own; presentations subscribe and decide nothing. This stands up the wire
shape only -- AbsorbEvent/GrabEvent/MeleeEvent/ThrustEvent plus a per-entity
CapabilityEvents source (UniRx Subject<T> per kind, matching the idiom
already used on Entity) -- with no publisher wired up yet. Compiles into the
headless Aetheria.Shared build with no Unity dependency.

AbsorbEvent carries the hit's DamageType (operator, 2026-09-17) so a future
presenter can vary visuals per type; the damage-type mask itself belongs to
the absorb capability's own data and is out of scope here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:36:14 +02:00
Meta Vens
47131ded68 Record the absorption damage type mask
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:26:30 +02:00
Meta Vens
646495f77a Record that presentation is chosen per item, independently of capability
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:10:24 +02:00
Meta Vens
ebba4a36b5 Test the IFF/grudge/detection rules and the weapon stance gate
IffAndCombatTests builds a minimal headless world (CultCache catalog, a Ship
hull and a gun with InstantWeaponData, an empty Zone/GalaxyZone) and pins:
override decides outright; clearing restores the derived rule; leaving the
zone clears overrides; the derived reciprocity path can't mirror another
entity's override; grudge triggers immediately whether the mark or the
detection happens second, is detection-gated, is sticky, and never fires for
a player-controlled entity; PerceivedStanceOf is unknown until detected;
Weapon.StanceAllowsFire and InstantWeapon.Trigger refuse to fire on a neutral
target and fire on a hostile one (and with no target at all); LockWeapon
builds lock on the shooter's own stance, not the target's; and
ZoneGenerator.EligibleWandererFactions excludes the owner and nearest faction
(and is empty when neither is eligible).

Adds one mutation per rule to tests/mutation_tests.py, all KILLED against
this HEAD (control still GREEN).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:01:31 +02:00
Meta Vens
a5fd8f1d1d Let the player target and mark neutrals, and fix the HUD lock direction
TargetReticle, TargetNext and TargetPrevious now consider all visible
entities except self (hostile and neutral), not just VisibleEnemies, so a
neutral wanderer can be selected. TargetNearest stays enemies-only (it drives
weapon lock) but now picks the nearest target: it was using MaxBy, which
picked the farthest.

Adds a "Toggle Stance" input action (bound to the N key; report names which)
that flips the player's own stance on the current target between hostile and
neutral via Entity.SetIff -- one button for the whole affordance, per the
operator's request. Also keeps the "iff hostile"/"iff neutral"/"iff clear"
console commands next to the existing ones, since they cost nothing extra
alongside the hotkey.

Fixes the HUD lock indicator's hostility check, which read
Target.IsHostileTo(CurrentEntity) instead of CurrentEntity.IsHostileTo(Target)
-- the same backwards direction as LockWeapon, fixed in the prior commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:01:15 +02:00
Meta Vens
89e9871092 Record the tractor beam as generic Tool gear
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:01:04 +02:00
Meta Vens
a351009956 Gate weapon fire on the shooter's own stance, shared by player and AI
Weapon.StanceAllowsFire is the one shared gate: true with no target, else the
shooter's own Entity.IsHostileTo(Target) -- not the target's stance toward the
shooter. It lives on the Weapon base class so every weapon subtype and every
caller (player input, TurretController, AI) goes through the same rule.

Wired at the two places weapons actually start dealing damage:
- InstantWeapon.Trigger (the base for InstantWeapon, AutoWeapon, ChargedWeapon
  and LockWeapon/Launcher, all of which route real firing through Trigger).
- ConstantWeapon.Execute's continuous-beam loop, which stops firing (and
  raises OnStopFiring) the moment the shooter's stance disallows it.

Also fixes LockWeapon's lock-building condition, which read the wrong
direction (target.IsHostileTo(shooter) instead of shooter.IsHostileTo(target))
-- the same backwards check as the HUD lock indicator, fixed alongside this in
ActionGameManager.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:00:59 +02:00
Meta Vens
2b33550cc1 Add manual IFF overrides, event-driven grudges, and detection-gated stance
Entity.SetIff(other, hostile?) is the one owner of a runtime-only, per-entity
override (a ReactiveDictionary, not persisted in packs). IsHostileTo checks it
first, but only for the top-level (non-recursive) query: the derived rule's
internal reciprocity check (recursive=true) skips overrides entirely, so one
entity's override can never leak into another entity's own derived hostility.
An override change refreshes EntityHostility immediately via a dedicated
subscription, so dependents (weapons, HUD, grudges) never need a tick to see
it. Overrides are cleared when an entity leaves the zone (existing removal
subscription).

Non-player entities (ships not IsPlayerShip, stations, turrets) hold a grudge:
WatchForGrudge subscribes to another entity's EntityHostility changes toward
this one, plus this entity's own VisibleEntities.ObserveAdd, so a hostile
stance grudges immediately whether the marking or the detection happens
second -- purely event-driven, no per-tick polling. A grudge only takes while
this entity currently detects the marker (VisibleEntities), and once set it
never re-evaluates (sticky; forgiveness is out of scope until a utility-
evaluation pass exists). Player-controlled entities never grudge automatically.

PerceivedStanceOf(other) exposes another entity's stance toward this one for
UI/AI, gated the same way: unknown (null) until this entity detects them.

Known follow-up: leaving the zone clears overrides, which also clears a
grudge. Left as a known gap per the operator's ruling, not solved here.
Of the per-tick derived-hostility refresh's inputs (Entity.cs ~955-964),
PresencePermitted is already a ReactiveProperty; Faction and zone ownership
are not, so converting that refresh to fully event-driven is future work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:00:41 +02:00
Meta Vens
f732cff9e6 Spawn neutral wanderers so combat has something non-hostile to target
ZoneGenerator now spawns NeutralWandererCount ships (default 2, authored on
ZoneGenerationSettings) from a galaxy faction that is neither the zone owner
nor its nearest faction, using the zone's existing seeded Random so placement
stays deterministic per zone. None spawn when no faction qualifies. The
faction-pool rule is pulled into ZoneGenerator.EligibleWandererFactions so it
is unit-testable without running the full generation pipeline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:00:22 +02:00
Meta Vens
de7b04c518 Record fork L: pickup is a capability, field shields are not universal
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:00:01 +02:00
Meta Vens
062d3fd1b2 Record that stance overrides clear on zone exit for now
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:56:25 +02:00
Meta Vens
9f80214492 Note drones as the driver for the parked electronic warfare work
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:27:48 +02:00
Meta Vens
f3917138fd Park the electronic warfare sensor ideas
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:25:47 +02:00
Meta Vens
67a953ee2d Record that stance signals are events gated by detection
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:22:10 +02:00
Meta Vens
0369e46f2f Record the stance rulings: toggle, weapons safe, no mirroring, grudges
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:18:32 +02:00
Meta Vens
46efccbec8 Record the Cut C follow-up and close the provenance schema cut
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:47:33 +02:00
Meta Vens
5e9634efcd Pin Brand's same-maker tie-break with a test and a mutant
Brand()'s ordering by record key was only exercised indirectly, through
a test that gives each candidate product a different maker. Add a test
with two FactionProductData for the same maker and same design, distinct
record keys, asserting Brand returns the one first in record-key order.

Add the corresponding mutant (OrderBy -> OrderByDescending at Brand's
ordering) to mutation_tests.py so a regression there gets caught.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:42:48 +02:00
Meta Vens
fc1c0526fe Census: list every distinct maker, don't invent a single one
MakerOf picked the first product for a design from any maker, the same
tie-break Brand() uses to resolve a runtime item's actual attribution.
That made census silently agree with an attribution it has no business
asserting on its own. Census's job is to report what the catalog says,
including when a design has more than one seller.

MakerOf now lists every distinct maker that sells a design (via its
products, manufacturer set), in record-key order, or "(none)" when no
product sells it. The existing duplicate (maker, design) detector is
unchanged. Verified byte-identical `census` output before and after,
since today's catalog has no design with two makers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:42:37 +02:00
Meta Vens
40055024b5 Record Soul passing provenance Cut C
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:37:11 +02:00
Meta Vens
7941aec861 Record role authoring as required Terminus content
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:35:47 +02:00
Meta Vens
0de7453953 Record the stats and power design direction as a target
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:31:37 +02:00