# Science

`PM.Science` covers the science and progression systems: experiments, the science regions that classify where you are, the discoverables you find in the world, and the tech tree that spends science points to unlock parts. It is a moderate-sized module with a separate entry point per content kind.

## The entry points

Science is several different assets, so there is a `Patch` method per kind rather than one:

* `PM.Science:PatchExperiments(name)` patches science experiments.
* `PM.Science:NewExperiment(name, callback)` creates a new experiment.
* `PM.Science:PatchRegions(name)` patches science regions.
* `PM.Science:PatchDiscoverables(name)` patches discoverables.
* `PM.Science:PatchTechNodes(name)` patches tech-tree nodes.
* `PM.Science:NewTechNode(name, callback)` creates a new tech node.
* `PM.Science:AddPartsToTechNode(nodeName, ...)` is a shortcut for unlocking parts.
* `PM.Science:DuplicateExperiments`, `DuplicateRegions`, `DuplicateDiscoverables` and `DuplicateTechNodes` copy each kind. See [Copying an existing asset](/doc/creating-assets-9bPpsuBU3M).

## Experiments

`PatchExperiments` targets an experiment by id. A common edit is appending a flavor description for a location the experiment was missing. This real patch fixes a gap on Pol:

```lua
PM.Science:PatchExperiments("CrewObservationPolMidlandsFlavorFix")
          :Named("CrewObservation")
          :Do(function(experiment)
              experiment.DataFlavorDescriptions:Append({
                  ResearchLocationID = "Pol_Landed_PolMidlands",
                  LocalizationTag = "Science/Experiments/CrewObservation/Data/Flavor/Pol_Landed_PolMidlands"
              })
          end)
```

`NewExperiment(name, callback)` creates a whole new experiment, filling in its data and sample values, descriptions, and transmission size.

## The tech tree

Tech nodes are where most science patches land, because reorganizing the tree and moving part unlocks is common. `PatchTechNodes` targets a node by id (`tNode_2v_rockets_01` and so on). Inside, you edit the node's prerequisites, science cost, position, and unlocked parts. This real patch fully reworks a node

```lua
PM.Science:PatchTechNodes("2VRockets01Reorder")
          :Named("tNode_2v_rockets_01")
          :Do(function(node)
              node.RequiredTechNodeIDs = { "tNode_1.5v_rockets_02" }
              node.RequiredSciencePoints = 230
              node.UnlockedPartsIDs:RemoveWhere(|id| id == "booster_1v_solid_kickback")
              node.UnlockedPartsIDs:Append("adapter_2v_methalox_1.5v-2v")
              node.TierToUnlock = 0
              node.TechTreePosition = { x = 1600.0, y = 750.0 }
          end)
```

Note the list operations on `UnlockedPartsIDs`: `:RemoveWhere` to drop a part, `:Append` to add one, or a whole-list assignment to replace it. These are the [JSON list methods](/doc/working-with-json-fWfY8KrejF).

## Unlocking parts the easy way

Adding parts to a node is so common it has a dedicated shortcut. `AddPartsToTechNode` takes the node id and any number of part ids, and appends them to that node's unlock list:

```lua
PM.Science:AddPartsToTechNode("tNode_1v_rockets_02",
    "booster_0v_srb_mantis",
    "booster_0v_srb_shrimp")
```

This is equivalent to a `PatchTechNodes` that appends to `UnlockedPartsIDs`, just shorter. Use it when all you are doing is unlocking parts.

## Unlocking variants

A node can also unlock [V-SwiFT](/doc/v-swift-part-variants-hYIodZNC2i) part variants. Its `UnlockedVariants` list names each variant by `PartName`, `VariantSetId` and `VariantId`. A variant that no node names is always selectable. If two nodes name the same variant, the first node that loads unlocks it. The R&D Center shows a node's variants after its parts, on a blue tile with a + badge.

Node JSON from an older bake has no `UnlockedVariants` list. Make the list before you append to it:

```lua
PM.Science:PatchTechNodes("MyMod_StripedTank")
    :Named("tNode_1v_rockets_01")
    :Do(function(node)
        if node.UnlockedVariants == nil then node.UnlockedVariants = J.Empty() end
        node.UnlockedVariants:Append({
            PartName = "fueltank_1v_inline_methalox_1x1",
            VariantSetId = "finish",
            VariantId = "striped",
        })
    end)
```

## Creating tech nodes

`NewTechNode(name, callback)` creates a new node. The name is the node's ID. The node starts as the game writes one: every field has its default value, and every list is empty. The callback sets the fields, and can append to the lists directly. The call returns the new asset, so you can gate it with `:Feature`.

```lua
PM.Science:NewTechNode("tNode_1v_electric_aviation_01", function(node)
    node.NameLocKey = "MyMod/TechNodes/Names/tNode_1v_electric_aviation_01"
    node.DescriptionLocKey = "MyMod/TechNodes/Descriptions/tNode_1v_electric_aviation_01"
    node.IconID = "ICO-RDCenter-Plane-24x"
    node.RequiredSciencePoints = 100
    node.RequiredTechNodeIDs:Append("tNode_1v_aero_02")
    node.TechTreePosition.x = 1080
    node.TechTreePosition.y = 90
    node.UnlockedVariants:Append({
        PartName = "engine_1v_turbofan_methane_wheesley",
        VariantSetId = "propulsion",
        VariantId = "electric",
    })
end):Feature("TechTree")
```

The node's name and description keys need rows in your mod's localization CSV.

## Regions and discoverables

`PatchRegions` patches science regions, the zones that set the per-situation science multipliers for a body. `PatchDiscoverables` patches discoverables, the anomalies you find in the world, keyed by region id with a position and detection radius. Both are indexed lists, so you reach an entry by its id and edit it. Their field forms are in `pm-science.d.lua`.

## Features

Science content belongs to the Redux [campaign features](/doc/campaign-features-9YIewcIArR):

| Content | Feature |
|---------|---------|
| Tech nodes | `Ksp2Redux:Nodes` |
| Experiments | `Ksp2Redux:Science` |
| Science regions and discoverables | `Ksp2Redux:Galaxy`, with the bodies they describe |

* To give a campaign pack its own tech tree, create its nodes and gate them on one of your features. If the stock tree must not load with it, do not put `Ksp2Redux:Nodes` in the features of the pack.
* To change regions or discoverables for some campaigns only, patch them in place under a [feature](/doc/features-geboWdIiDN). A rescaled body usually needs changes to both, because region altitudes and discoverable positions are in meters:

```lua
PM:WithFeature("Rescale", function()
    PM.Science:PatchRegions("RescaleKerbinRegions")
              :Late()
              :Named("kerbin_science_regions")
              :Do(function(regions)
                  regions.SituationData.HighOrbitMaxAltitude = regions.SituationData.HighOrbitMaxAltitude * 2.5
              end)
end)
```

The `Duplicate...` methods copy an asset under a new name. The copy keeps the internal ids of the asset. Thus a copy of the regions of a body is a second definition of the same regions. Use these methods only to start new content.