• Welcome to the new COTI server. We've moved the Citizens to a new server. Please let us know in the COTI Website issue forum if you find any problems.
  • We, the systems administration staff, apologize for this unexpected outage of the boards. We have resolved the root cause of the problem and there should be no further disruptions.

REST API for Starship Component creation

robject

SOC-14 10K
Admin Award
Marquis
I want to discuss this, because many pairs of eyes can see better than one pair.
Problem solving can be parallelizable.

POST /sensors/{type}
Create a sensor. Type is the sensor type code from the rules. In T5, it's a single capital letter. It could be whatever.

Request Body:
Traveller5 modifies sensors based on the sensor's technological stage, the mount, and the mount's range. They're all optional.

Code:
stage (optional): enum (Exp | Pro | Ear | Bas | Std | Imp | Mod | Adv | Ult)
mount (optional): enum (T1 | T2 | T3 | T4 | B1 | B2)
range (optional): enum (Vl | D | Vd | Or | Fo | G | BR | FR | SR | AR | LR | DS)

Response Body:
The calculated stats for the requested sensor.
All of these are required (for T5).

example:
Code:
{
   "type":"N",
   "name":"Neutrino Detector",
   "label":"AR T1 Neutrino Detector-10",
   "stage":"Std",
   "mount":"T1",
   "range":"AR",
   "tl":10,
   "tons":1,
   "mcr":1.2
}


GET /sensors/all

No params.

Response:
Code:
{
    "A": {
        "type": "A",
        "name": "Activity Sensor",
        "tl": 11,
        "mcr": 0.1,
        "class": "W"
    },
    "C": {
        "type": "C",
        "name": "Communicator",
        "tl": 8,
        "mcr": 1,
        "class": "S"
    },
    "E": {
        "type": "E",
        "name": "EMS",
        "tl": 12,
        "mcr": 1,
        "class": "S"
    },
    "G": {
        "type": "G",
        "name": "Grav Sensor",
        "tl": 13,
        "mcr": 1,
        "class": "S"
    },
    "H": {
        "type": "H",
        "name": "HoloVisor",
        "tl": 18,
        "mcr": 1,
        "class": "S"
    },

...etc etc...

}
 
Last edited:
Initial reaction:

Headers
I'm using Traveller5 sensors as an example, but an API isn't limited to one ruleset. Think about that. Would you put the ruleset in the header?

Code:
traveller-ruleset:   enum (ct | mt | tne | 1248 | mgt1 | mgt2 | t5 | cepheus)

A version is probably a good idea, but that is too coarse. The rest of the discussion is very specific to T5.

Code:
traveller-ruleset:   enum (5.00 | 5.09 | 5.10)
would be more appropriate. You could make an API to handle all flavours of T5?



Request Body:
Traveller5 modifies sensors based on the sensor's technological stage, the mount, and the mount's range. They're all optional.

Code:
stage (optional): enum (Exp | Pro | Ear | Bas | Std | Imp | Mod | Adv | Ult)
mount (optional): enum (T1 | T2 | T3 | T4 | B1 | B2)
range (optional): enum (Vl | D | Vd | Or | Fo | G | BR | FR | SR | AR | LR | DS)
mount is not optional, but I guess it can be defaulted.
range should be a part of the response, range effect should be a parameter [-3,3]. The user should not need to keep track of space range vs world range sensors.

Perhaps a default selector regime, e.g. cheap, standard, good? That way a Free Trader could get cheap sensors and a warship could get good sensors with stage and range effect chosen for max sensor task DM?

Max TL should be a parameter? To check the result, and form a max on the stage effect. User selections may need to be overridden, e.g. if the TL of the ship or component is changed.

The base TL of the sensor type should already be known, in order to guide the user selections of Range effect and mod? Could be solved by a default call to fill out the UI, with a second call when the user has selected?

mount is truncated, there are many more mounts. Should you use the same mount enum/table for sensors/weapons/screens, I think that would make it easier to keep track of hardpoints? In such case some values will be off-limits for some types. The default mount will differ for different types, and different types of weapons.

There is no need to limit the range parameter to a few select ranges, we might want to add some special types of sensors later, e.g. long range world scanners. Just use world range as a natural number, since space range is just world range - 5. We might want to translate that to a string in the presentation layer, but an enum is inappropriate.


Response Body:
The calculated stats for the requested sensor.
All of these are required (for T5).

Code:
type: sensor code
name: the sensor's actual name
label: the full "catalog" listing of the sensor, taking mods into account. 
stage: the sensor's stage code
mount: the sensor's mount code
range: the sensor's range code
tl: its TL
tons: its volume
mcr: its cost

example:
Code:
{
   "type":"N",
   "name":"Neutrino Detector",
   "label":"AR T1 Neutrino Detector-10",
   "stage":"Std",
   "mount":"T1",
   "range":"AR",
   "tl":10,
   "tons":1,
   "mcr":1.2
}

There are more parameters.
Base TL of sensor (for the UI, see above).
Perhaps long name and short name, "AR T1 Neutrino Detector-10" vs "AR T1 Neutr-10"?
Generic name for descriptions, e.g. "Neutrino Detector"?
Operating principle?
Active, passive, or both?
Effective range to see an ACS?
Effective sensor task DM?
Sensor task active DM?
Mod DM?
The user needs to see that to make an informed decision. The computer is much more suited to keep track of that, rather than forcing the user to sit with the rule book to look it up...

Perhaps a suggested substitution sensor, e.g. perhaps an EMS would be better than the selected Radar for this TL/range?



Perhaps a separate call to get the available sensor types, with name, code letter, base TL, base range? That way the rest of the code base would never need to know or keep track of such minutiæ.
 
Last edited:
...enum...

Sorry about that. That's API-speak for "these are strings that form a constrained set of acceptable values" or something like that. The payloads are JSON (and MIME...), which of course has no concept of enum types.

The user should not need to keep track of space range vs world range sensors.
Perhaps a separate call to get the available sensor types, with name, code letter, base TL, base range? That way the rest of the code base would never need to know or keep track of such minutiæ.

That's true.

The endpoint ^up there^ is for constructing a single Sensor... and it assumes you know what you're doing.

(Side note: I had an internal struggle about whether or not this was a POST, or if it was actually a GET. In the end I didn't want URL params so I made it a POST.)

A different endpoint would return pick lists. "get all sensors". This is probably the most useful, in that a minimum viable application must provide choices primarily, and only secondarily would it offer customization by tweaking the innards.

Note that filtering responses is what a UI does well. And at the end of the day, these are ship designers; they know what they want. So a max TL thing is probably not useful on the API.

Operating principle?
Active, passive, or both?
Effective range to see an ACS?
Effective sensor task DM?
Sensor task active DM?
Mod DM?

Those are interesting and useful. I'll keep them in mind.
 
Last edited:
At best I can offer this example.

This is creating a mass driver from FF&S:

Code:
(defun make-mass-driver (&key tl          ; tech-level 8-16+
                           diameter         ; barrel diameter in cm
                           is-carriage-mounted ; T if carriage, else vehicle
                           has-gun-shield ; T if equipped w/Gun Shield
                           has-autoloader ; T if equipped w/Auto Loader
                           has-direct-fire-sight ; T if equipped w/DF sight
                           has-indirect-fire-sight; T if equipped w/IDF sight
                           has-point-defense ; T if ballistic computer PD
                           ;; optional, derived values
                           muzzle-velocity 
                           projectile-mass
                           muzzle-energy
                           required-energy
                           homopolar-generator ; an HPG
                           direct-fire-sight
                           indirect-fire-sight
                           ballistic-computer
                           mass
                           volume
                           price)
  (default muzzle-velocity
      (%mass-driver-muzzle-velocity tl))
  (default projectile-mass
      (%mass-driver-projectile-mass (/ diameter 2)))
  (default muzzle-energy
      (%mass-driver-muzzle-energy muzzle-velocity projectile-mass))
  (default required-energy
      (* muzzle-energy (%mass-driver-energy-efficiency tl)))
  (default homopolar-generator
      (make-homopolar-generator :tl tl :mj required-energy))
  (when has-direct-fire-sight 
    (default direct-fire-sight (make-direct-fire-sight :tl tl)))
  (when has-indirect-fire-sight
    (default indirect-fire-sight (make-indirect-fire-sight :tl tl)))  
  (default ballistic-computer
      (make-ballistic-computer :tl tl
                               :point-defense has-point-defense))
  (unless mass
    (let* ((m 0)
           (wg (+ (* diameter diameter) 50))
           (fcg (+ (if direct-fire-sight  ;; fire control weight
                       (direct-fire-sight-mass direct-fire-sight)
                       0)
                   (if indirect-fire-sight
                       (indirect-fire-sight-mass indirect-fire-sight)
                       0)
                   (ballistic-computer-mass ballistic-computer)))
           (carriage-weight (if is-carriage-mounted
                                (max (/ muzzle-energy 4) wg)
                                0))
           (gun-shield-weight (if has-gun-shield
                                  (* diameter 0.07)
                                  0))
           (al-weight (if has-autoloader
                          (* 30 projectile-mass)
                          0)))
      (setf mass (+ wg fcg carriage-weight gun-shield-weight al-weight))))
  (default volume mass)
  
  (%make-mass-driver :tl tl :diameter diameter
		     :is-carriage-mounted is-carriage-mounted
                     :has-gun-shield has-gun-shield
		     :has-autoloader has-autoloader
		     :muzzle-velocity muzzle-velocity 
		     :projectile-mass projectile-mass
		     :muzzle-energy muzzle-energy
		     :required-energy required-energy
		     :homopolar-generator homopolar-generator
                     :direct-fire-sight direct-fire-sight
                     :indirect-fire-sight indirect-fire-sight
                     :ballistic-computer ballistic-computer
                     :mass mass
                     :volume volume))

I want to point out a couple of things.

First, the "required" parameters are TL and diameter. After that there's some options represented as flags: is-carriage-mounted, has-gun-shield, has-autoloader, has-direct-fire-sight, has-indirect-fire-sight, has-point-defense.

After that, the parameters are overrides to internal computed values. TL normally dictates muzzle velocity for example, but if you want to tweak that, go right ahead.

A simple example:
(make-mass-driver :tl 14 :diameter 10 :has-direct-fire-sight T)

Resulting in:

Code:
(MASS-DRIVER 
    :TL 14 
    :DIAMETER 10 
    :IS-CARRIAGE-MOUNTED NIL 
    :HAS-GUN-SHIELD NIL 
    :HAS-AUTOLOADER NIL 
    :MUZZLE-VELOCITY 6000
    :PROJECTILE-MASS 39.26990816987241548L0 
    :MUZZLE-ENERGY 706.85834 
    :REQUIRED-ENERGY 1130.9734
    :HOMOPOLAR-GENERATOR #S(HOMOPOLAR-GENERATOR 
            :TL 14 
            :MJ 1130.9734 
            :MASS 90.48 
            :VOLUME 45.24 
            :PRICE 0.45)
    :DIRECT-FIRE-SIGHT #S(DIRECT-FIRE-SIGHT 
            :TL 14 
            :TYPE "EMS RF" 
            :RANGE 3 
            :MASS 0.08 
            :VOLUME 0.08 
            :PRICE 0.02)
    :INDIRECT-FIRE-SIGHT NIL
    :BALLISTIC-COMPUTER #S(BALLISTIC-COMPUTER 
            :TL 14
            :TYPE "Synaptic" 
            :MASS 0.03 
            :VOLUME 0.03 
            :PRICE 0.5 
            :POINT-DEFENSE NIL 
            :DIFF-MOD 5) 
    :MASS 150.11
    :VOLUME 150.11
    :PRICE NIL)

There's a few takeaways from this (not necessarily obvious).

One, each mass driver requires a Homopolar Generator. When you create a mass driver, you can specify your own, or it will make one for you. Similarly you can specify a direct fire sight, or it will just make one based on the TL of the gun, same with the ballistic computer.

Simply, there are several options. Many can be derived, but if you want to override these, then you can. (I note that I managed to not have the total price calculate -- that's an oversight).

On all of my subsystems, you can use the defaults from the tables, or specify them. If you want to make a direct fire sight that weighs 1 ton, then it'll accommodate you.

(make-mass-driver :tl 14 :diameter 10 :has-direct-fire-sight T
:direct-fire-sight (make-direct-fire-sight :tl 14 :mass 1))

All I'm saying that in this "simple" case, you can just ask for a TL 14, 10cm mass driver. But each mass driver consists of several subsystems, which can be either derived or specified.

You can readily see this trivially becoming something like:

GET /ffs/mass-driver?tl=14&diameter=10&has-direct-fire-sight=t

and getting back:
Code:
{
    "TL": 14,
    "DIAMETER": 10,
    "MUZZLE-VELOCITY": 6000,
    "PROJECTILE-MASS": 39.26990816987241548,
    "MUZZLE-ENERGY": 706.85834,
    "REQUIRED-ENERGY": 1130.9734,
    "HOMOPOLAR-GENERATOR": {
        "TL": 14,
        "MJ": 1130.9734,
        "MASS": 90.48,
        "VOLUME": 45.24,
        "PRICE": 0.45
    },
    "DIRECT-FIRE-SIGHT": {
        "TL": 14,
        "TYPE": "EMS RF",
        "RANGE": 3,
        "MASS": 0.08,
        "VOLUME": 0.08,
        "PRICE": 0.02
    },
    "BALLISTIC-COMPUTER": {
        "TL": 14,
        "TYPE": "Synaptic",
        "MASS": 0.03,
        "VOLUME": 0.03,
        "PRICE": 0.5,
        "DIFF-MOD": 5
    },
    "MASS": 150.11,
    "VOLUME": 150.11,
    "PRICE": 0
}

What's less obvious is how do you create a mass driver with a different subsystem. (Why would you is a different problem.)

And then extrapolate that problem throughout the entire domain. This is just a single gun.
 
I suggest the response would probably benefit from being identical in format for all possible objects. Just the values would differ. This would help keep the API uniform and simplify the documentation.

Some of the detail might be better dealt with in a free text parameter, as they provide information to the user but don't impact the ship design stats. For example, modifiers are only used to choose a widget, they don't add to the ship design.
 
That's API-speak for "these are strings that form a constrained set of acceptable values" or something like that. The payloads are JSON (and MIME...), which of course has no concept of enum types.
Much easier to validate an enum (numerical parameter), than a random string?


... and it assumes you know what you're doing.
Users and GUI designers? Really?


Note that filtering responses is what a UI does well. And at the end of the day, these are ship designers; they know what they want.
Off-loading business logic to the UI without validation seems risky. I would let the sensor component be the final authority on what a correct sensor is.
 
Pedantically, it should just be "/sensors"

and then "/sensors/A" would return:

Code:
    {
        "type": "A",
        "name": "Activity Sensor",
        "tl": 11,
        "mcr": 0.1,
        "class": "W"
    }

Thank you, you're right. "Get the collection of sensors".
 
Users and GUI designers? Really?

LOL Ah, yes, I know what you're thinking. Well, you have to admit there's a tension between the model, view, and controller.

Off-loading business logic to the UI without validation seems risky. I would let the sensor component be the final authority on what a correct sensor is.

That sounds correct to me.
 
The only way I see this working is if there's a Ship Evaluation Service, but, boy, that seems like a bunch of potentially redundant work.

Simply, the SES gets passed a ship and returns "ship wide" effects.

For example, it could return the total cost. It could return the G acceleration giving the mass of the ship. It could return a list of warnings or rule violations.

Would the SES have a list of valid sensors in order to validate that the sensor installed in the ship is OK? I would have to say "yes". But it just shows that the SES has to kind of know "everything". But how much does the SES take for granted from the submitted ship.

If I send it a 10KTon battle cruiser with a 5 dTon Jump 8 drive costing 0.1MCr, does the SES say "Hey, there's no J8 drive, and boy, this is much to small". Or does it assume "Wow, don't know where they got this thing, but they sure got a good deal -- must have been from one of those fringe systems."

The workflow may well be a Client assembles random subsystems and ship the whole kit to the SES to learn about gaps and other such things.

But now, for example, back to the Jump drive example, the Jump Drive code needs to know all about Jump Drive calculations, but now also so does the SES in order to validate that the provided drive is in line with those calculations. The SES could simply invoke the Jump service and compare the results, I suppose.

The SES can be quite involved. It could just take the ship as design, feed all of the base parameters in to the calculation services, and compare the values.

Or, a ship can just be a collection of systems -- with no costs, no mass, no volume.

So, to the SES, you send "TL-12 Wedge Hull 1000 dton J3 M4 P10 10 Triple Beam Turrets, 10 Armor" and the SES comes back and says "Youre missing 400 tons of fuel tankage, need XXX crew, but so far this costs YYYMCr" with a detailed breakdown by subsystem.

The issues can iterate for example. First pass it says "You need XXX Crew". You then pass back the number and roles of the crew, and then it says "You need 2 more engineering, and 15 double occupancy staterooms". Can't calculate stateroom requirements with no crew.

Let the SES figure out all the of actual values based on the specified components, and if it comes back green with no issues, "it's done".

So in the end, the large SES does all of the math and rule checks based on raw designs.

You want to pass it a Sensor B, it'll simply complain "Don't know what a sensor B is".

Where the client doesn't necessarily need to know the interrelationships of the components. With Book 2 SES, you can submit a ship that's 2000 tons with an M Drive A, it'll just complain that the drive is inadequate. The Client doesn't necessarily have to know what drives a 2000 ton ship can use. So the client just has "Hull size" and offer up A-Z for drive choice. It's not as friendly as it could be, but there ya go.
 
Pushed the current service up to github. https://github.com/bobbyjim/acs-builder

Added some utility endpoints, for alternate ways to beef up the data maps.

SENSORS

GET /sensors
Returns a list of all sensors in the database.

POST /sensors
Add a sensor to the database.

POST /sensors/{type}
Build a sensor from existing mounts, ranges, and types.

Request Body:

Code:
{
   "mount": "T1",  // optional. any mount.
   "range": "AR" // optional. any range.
}

No mount/range checking at this time, but the data supports it.

WEAPONS

GET /weapons
Returns a list of all weapons in the database.

POST /weapons
Add a weapon to the database.

POST /weapons/{type}
Build a weapon from existing mounts, ranges, and types. Uses the same request body as the sensor.


MOUNTS

GET /mounts
Returns a list of all mounts in the database.

POST /mounts
Add a mount to the database.


RANGES

GET /ranges
Returns a list of all ranges in the database.

POST /ranges
Add a range to the database.
 
Added a "Drives" endpoint.

POST /drives/{type}
Builds a drive of the given type.

Request body:
Code:
{
    "rating": 2,
    "targetHullVolume": 200
}

Calculates volume, cost, and basic fuel needs based on the requested rating and the target hull volume. If the volume is too small, it adjusts to the minimum and re-calculates the drive stats based on that.

Example response body:

Code:
{
    "type": "J",
    "name": "Jump",
    "label": "Jump-2",
    "rating": 2,
    "tons": 15,
    "mcr": 15,
    "targetHullTons": 200,
    "tonsMinimum": 10,
    "mcrPerTon": 1,
    "fuel": 40
}
 
Last edited:
If I send it a 10KTon battle cruiser with a 5 dTon Jump 8 drive costing 0.1MCr, does the SES say "Hey, there's no J8 drive, and boy, this is much to small". Or does it assume "Wow, don't know where they got this thing, but they sure got a good deal -- must have been from one of those fringe systems."

The /drives endpoint works like this: "Gimme a Jump-8 drive for my 10kt ship"

Code:
POST /drives/J

{
   "rating": 8,
   "targetHullTons": 10000
}

and the response is "Here's what I came up with"

Code:
{
    "type": "J",
    "name": "Jump",
    "label": "Jump-8",
    "rating": 8,
    "tons": 2005,
    "mcr": 2005,
    "targetHullTons": 10000,
    "tonsMinimum": 10,
    "mcrPerTon": 1,
    "fuel": 8000
}

Take what you need from the response, or slap the whole thing into its slot in the ship map and let the View take care of visible fields.

The View is likely to be spreadsheet-like, and that's the right place to show volume surplus or deficit.
 
Added two more queries:

GET /hulls
GET /drives

Gets a list of the hull configurations supported, and gets a list of the hull drive types supported.

Added a hull builder.

POST /hull/{config}
e.g. POST /hull/L to build a Streamlined hull.

Request body example:
Code:
{
    "tons": 100,
    "tl": 10
}

Response example:
Code:
{
    "config": "L",
    "name": "Lifting Body",
    "tl": 10,
    "tons": 100,
    "mcr": 16,
    "mcrPer100Tons": 12
}
 
In its current state, you could:

(1) query the hull types available.
(2) pick one and create a hull with a given config, volume, and TL.

(3) query the drive types available.
(4) pick out some drives and build them based on the ship volume and desired performance.

(5) display the choices, run the totals, and alert the operator if tonnages go over.
(6) rebuild the installed drives dynamically, if the user changes the hull tonnage.

(7) query the sensors available

*** (8) I want the list of valid mounts for a given sensor.
*** (9) I want to query the list of sensors across all ranges based on a given a sensor type and mount.

(Then, similarly for weapons and defenses.)
 
SENSORS

POST /sensors/{type}
Build a sensor from existing mounts, ranges, and types.

Request Body:

Code:
{
   "mount": "T1",  // optional. any mount.
   "range": "AR" // optional. any range.
}

Mount: not all mounts are legal, e.g. Bolt-in.
Range: not all ranges are legal, and not for all sensors types.
Stage: missing, but needed?

Additional mount properties such as Extendable and Deployable missing, but needed. Only valid for some mount types; turrets.

Note that some sensor types can have extra limitations, such as maximum range, see B2 p140-142.


You should probably send Range Effect as a parameter, and return actual resulting range as a respond parameter?


Something like:
Code:
{
   "mount": "T1",  // optional. valid sensor mount, default is "Surface".
   "range-effect": "1" // optional. -3 to +3, 0 is no range effect.
   "stage": "1" // optional. -3 to +4, 0 is no stage effect.
   "extendable": "1" // optional. the mount is made extendable, if possible.
   "deployable": "1" // optional. the mount is made deployable, if possible.
}

Not all range effects are correct for all sensor types, e.g. range effect -3 is not valid for world range sensors and should be treated as range effect -2.

Space range and World range sensors use different range effect tables.

Whether the sensor is Space range or World Range, and whether it can be modifiable (default sensors) should be returned by the GET sensors call.



attributed to Albert Einstein said:
Everything should be made as simple as possible, but not simpler.

I think you are making your life a little too easy by disregarding necessary detail at the design stage...
 
Code:
POST /drives/J

{
   "rating": 8,
   "targetHullTons": 10000
}

TL availability?
Stage?
Power availability? Not all available jump drives can be powered at the same TL, especially if using Anti-matter or Collector power.
Using Drive Table or EP calculation? Not all potentials are available if using tech stages.
 
POST /hull/{config}
e.g. POST /hull/L to build a [o]Streamlined[/o] Lifting Body hull.

Request body example:
Code:
{
    "tons": 100,
    "tl": 10
}

Response example:
Code:
{
    "config": "L",
    "name": "Lifting Body",
    "tl": 10,
    "tons": 100,
    "mcr": 16,
    "mcrPer100Tons": 12
}

You also need to specify Structure, which can affect hull cost and armour size.

You need to include hull features such as legs, wings, fins, floatation, and lifters. Some features are included at no cost for some configurations. Some features may affect resulting hull size (for some configurations), e.g. wings adds to hull size for Streamlined, are included in Airframe, but not allowed for Cluster configuration.
 
Back
Top