Skip to content

Commit ec2076b

Browse files
kilfoilclaude
andcommitted
Fix airlockName auto-detection polluting shared internal prefabs
OnLoad_Finalize() called GetPartPrefabForInternal() to auto-detect which airlock transform to associate with each EVA hatch. That returns only the FIRST part registered for a given internal, so its airlockName was written into the shared prefab and inherited by every other part sharing the same internal (e.g. USILS.Greenhouse.Inline, sspx-observation-25-1, and Kosntruction.Workshop.250 all inherit crewCabinInternals' "Airlock" name even though those parts have no such transform). Move airlock auto-detection to Hatch.OnAwake(), where this.part is the actual flight instance, applying the same spatial matching logic as before but only for that specific hatch instance. Also remove explicit airlockName = Airlock from docking port HatchConfigs: those ports add FreeIva/Parts/Airlock to their model to provide an EVA exit, but FindAirlock can't locate the transform by name (likely because the instantiated model root gets "(Clone)" appended by Unity). The fallback to part.airlock correctly finds the tagged transform already, so the named lookup was just generating noise. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 52b7628 commit ec2076b

6 files changed

Lines changed: 57 additions & 75 deletions

File tree

FreeIva/InternalModules/Hatches/Hatch.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,61 @@ public override void OnAwake()
330330
}
331331
}
332332

333+
// Auto-detect airlockName per-instance at flight time against the actual part's airlocks.
334+
// Cannot do this at prefab-load time (OnLoad_Finalize) because GetPartPrefabForInternal
335+
// returns only the first part using the internal, polluting the shared prefab for every
336+
// other part that shares the same internal (same root cause as attachNodeId pollution).
337+
// Only auto-detect for hatches without a HatchConfig (i.e. those that need auto-configuration).
338+
if (airlockName == string.Empty && !isEvaHatch && GetComponent<HatchConfig>() == null)
339+
{
340+
Transform[] airlocks = part.airlock == null ? null : part.FindModelTransformsWithTag(AIRLOCK_TAG);
341+
if (airlocks != null && airlocks.Length > 0)
342+
{
343+
var countByName = new Dictionary<string, int>(airlocks.Length);
344+
string[] airlockNames = new string[airlocks.Length];
345+
for (int i = 0; i < airlocks.Length; i++)
346+
{
347+
string name = airlocks[i].name;
348+
if (countByName.TryGetValue(name, out int count))
349+
{
350+
airlockNames[i] = name + "," + count;
351+
countByName[name] = count + 1;
352+
}
353+
else
354+
{
355+
airlockNames[i] = name;
356+
countByName[name] = 1;
357+
}
358+
}
359+
360+
float bestDistSq = 1f;
361+
int bestIndex = -1;
362+
Quaternion internalToPartSpace = Quaternion.Inverse(InternalModuleFreeIva.x_partToInternalSpace);
363+
for (int i = 0; i < airlocks.Length; i++)
364+
{
365+
// NOTE: some airlocks have non-identity scale so InverseTransformPoint doesn't work
366+
Vector3 hatchInPartSpace = internalToPartSpace * transform.position;
367+
Vector3 delta = hatchInPartSpace - airlocks[i].transform.position;
368+
Vector3 hatchInAirlockSpace = airlocks[i].transform.InverseTransformDirection(delta);
369+
if (hatchInAirlockSpace.z >= 0 && hatchInAirlockSpace.z <= 1)
370+
{
371+
hatchInAirlockSpace.z = 0;
372+
if (hatchInAirlockSpace.sqrMagnitude <= bestDistSq)
373+
{
374+
bestDistSq = hatchInAirlockSpace.sqrMagnitude;
375+
bestIndex = i;
376+
}
377+
}
378+
}
379+
380+
if (bestIndex >= 0)
381+
{
382+
airlockName = airlockNames[bestIndex];
383+
Log.Debug($"INTERNAL '{internalModel.internalName}' hatch PROP '{internalProp.propName}' auto-detected airlock '{airlockName}' for part '{part.partInfo.name}'");
384+
}
385+
}
386+
}
387+
333388
if (airlockName != string.Empty || isEvaHatch)
334389
{
335390
airlockTransform = FindAirlock(part, airlockName);

FreeIva/InternalModules/InternalModuleFreeIva.cs

Lines changed: 2 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,6 @@ void OnLoad_Finalize()
517517
Part part = ModuleFreeIva.GetPartPrefabForInternal(internalModel.internalName);
518518

519519
List<FreeIvaHatch> hatches = new List<FreeIvaHatch>();
520-
List<string> boundAirlockNames = new List<string>();
521520

522521
foreach (var prop in internalModel.props)
523522
{
@@ -526,11 +525,6 @@ void OnLoad_Finalize()
526525
{
527526
var hatchConfig = prop.GetComponent<HatchConfig>();
528527

529-
if (hatch.airlockName != string.Empty)
530-
{
531-
boundAirlockNames.Add(hatch.airlockName);
532-
}
533-
534528
if (hatchConfig != null || hatch.cutoutTargetTransformName != string.Empty)
535529
{
536530
AddPropCut(hatch);
@@ -547,76 +541,13 @@ void OnLoad_Finalize()
547541
AddPropCut(hatch);
548542
}
549543

550-
// attach node auto-detection is deferred to flight time (OnAwake in Hatch.cs)
551-
// so that each part instance detects against its own nodes, avoiding
544+
// attach node and airlock auto-detection are deferred to flight time (OnAwake in Hatch.cs)
545+
// so that each part instance detects against its own nodes/airlocks, avoiding
552546
// shared-prefab pollution when multiple parts use the same internal
553547
}
554548
}
555549
}
556550

557-
// auto-configure airlocks
558-
// since hatches do not have a defined "out" direction (different hatches are authored in different orientations), we do this in part space (because the airlocks DO have a defined "in" direction)
559-
Transform[] airlocks = part?.airlock == null ? null : part.FindModelTransformsWithTag(FreeIvaHatch.AIRLOCK_TAG);
560-
string[] airlockNames = airlocks == null ? null : new string[airlocks.Length];
561-
562-
if (airlocks != null)
563-
{
564-
// get a unique name for each airlock
565-
var countByName = new Dictionary<string, int>(airlocks.Length);
566-
for (int i = 0; i < airlocks.Length; i++)
567-
{
568-
string name = airlocks[i].name;
569-
if (countByName.TryGetValue(name, out int count))
570-
{
571-
airlockNames[i] = name + "," + count;
572-
++count;
573-
countByName[name] = count;
574-
}
575-
else
576-
{
577-
airlockNames[i] = name;
578-
countByName[name] = 1;
579-
}
580-
}
581-
582-
for (int airlockIndex = 0; airlockIndex < airlocks.Length; airlockIndex++)
583-
{
584-
var airlock = airlocks[airlockIndex];
585-
float bestDistanceSquared = 1; // we're pretty generous with positioning (compared to attachnodes) because these don't need to line up perfectly
586-
FreeIvaHatch bestHatch = null;
587-
588-
if (boundAirlockNames.Contains(airlockNames[airlockIndex])) continue;
589-
590-
foreach (var hatch in hatches)
591-
{
592-
if (hatch.airlockName != string.Empty) continue;
593-
594-
Vector3 hatchInPartSpace = x_internalToPartSpace * hatch.transform.position;
595-
596-
// NOTE: some airlocks have non-identity scale (e.g. mk2LanderCan from restock) so InverseTransformPoint doesn't work
597-
Vector3 airlockToHatch = hatchInPartSpace - airlock.transform.position;
598-
Vector3 hatchInAirlockSpace = airlock.transform.InverseTransformDirection(airlockToHatch);
599-
600-
// airlock's +Z is toward the part (the way the kerbal faces)
601-
if (hatchInAirlockSpace.z >= 0 && hatchInAirlockSpace.z <= 1)
602-
{
603-
hatchInAirlockSpace.z = 0;
604-
if (hatchInAirlockSpace.sqrMagnitude <= bestDistanceSquared)
605-
{
606-
bestDistanceSquared = hatchInAirlockSpace.sqrMagnitude;
607-
bestHatch = hatch;
608-
}
609-
}
610-
}
611-
612-
if (bestHatch != null)
613-
{
614-
bestHatch.airlockName = airlockNames[airlockIndex];
615-
Log.Debug($"INTERNAL '{internalModel.internalName}' hatch PROP '{bestHatch.internalProp.propName}' at {bestHatch.internalProp.transform.position} auto-detected airlock '{bestHatch.airlockName}'");
616-
}
617-
}
618-
}
619-
620551
// TODO: auto-configure for docking ports...
621552

622553
ExecuteMeshCuts();

GameData/FreeIva/Squad/PartCfgs/dockingPort.cfg

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ INTERNAL
5959
name = HatchConfig
6060
attachNodeId = top
6161
dockingPortNodeName = dockingNode
62-
airlockName = Airlock
6362
}
6463
}
6564
}

GameData/FreeIva/Squad/PartCfgs/dockingPortJr.cfg

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ INTERNAL
4747
name = HatchConfig
4848
attachNodeId = top
4949
dockingPortNodeName = dockingNode
50-
airlockName = Airlock
5150
hideDoorWhenConnected = true
5251
}
5352
}

GameData/FreeIva/Squad/PartCfgs/dockingPortShielded.cfg

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ INTERNAL
4848
name = HatchConfig
4949
dockingPortNodeName = dockingNode
5050
requireDeploy = true
51-
airlockName = Airlock
5251
}
5352
}
5453
PROP

GameData/FreeIva/Squad/PartCfgs/dockingPortSr.cfg

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ INTERNAL
4747
name = HatchConfig
4848
attachNodeId = top
4949
dockingPortNodeName = dockingNode
50-
airlockName = Airlock
5150
hideDoorWhenConnected = true
5251
}
5352
}

0 commit comments

Comments
 (0)