view src/p2_deco.c @ 584:d63dec562d50

CNS fix
author heinrichsweikamp
date Wed, 28 Feb 2018 10:24:54 +0100
parents b455b31ce022
children 00ad4ffd915b
line wrap: on
line source

// ***************************************************************************
// p2_deco.c                                         REFACTORED VERSION V2.98
//
//  Created on: 12.05.2009
//  Author: heinrichs weikamp, contributions by Ralph Lembcke and others
//
// ***************************************************************************

//////////////////////////////////////////////////////////////////////////////
// OSTC - diving computer code
// Copyright (C) 2011 HeinrichsWeikamp GbR
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//////////////////////////////////////////////////////////////////////////////

// history:
// 01/03/08 v100: first release candidate
// 03/13/08 v101: start of programming ppO2 code
// 03/13/25 v101a: backup of interim version with ppO2 calculation
// 03/13/25 v101: open circuit gas change during deco
// 03/13/25 v101: CNS_fraction calculation
// 03/13/26 v101: optimization of tissue calc routines
// 07/xx/08 v102a: debug of bottom time routine
// 09/xx/08 v102d: Gradient Factor Model implementation
// 10/10/08 v104: renamed to build v103 for v118 stable
// 10/14/08 v104: integration of char_I_depth_last_deco for Gradient Model
// 03/31/09 v107: integration of FONT Incon24
// 05/23/10 v109: 5 gas changes & 1 min timer
// 07/13/10 v110: cns vault added
// 12/25/10 v110: split in three files (deco.c, main.c, definitions.h)
// 2011/01/20: [jDG] Create a common file included in ASM and C code.
// 2011/01/24: [jDG] Make ascenttime an short. No more overflow!
// 2011/01/25: [jDG] Fusion deco array for both models.
// 2011/01/25: [jDG] Use CF(54) to reverse deco order.
// 2011/02/11: [jDG] Reworked gradient-factor implementation.
// 2011/02/15: [jDG] Fixed inconsistencies introduced by gas switch delays.
// 2011/03/21: [jDG] Added gas consumption (CF56 & CF57) evaluation for OCR mode.
// 2011/04/15: [jDG] Store low_depth in 32bits (w/o rounding), for a better stability.
// 2011/04/25: [jDG] Added 1mn mode for CNS calculation, to allow it for deco planning.
// 2011/04/27: [jDG] Fixed char_O_gradient_factor calculation when model uses gradient-factor.
// 2011/05/02: [jDG] Added "Future TTS" function (CF58).
// 2011/05/17: [jDG] Various cleanups.
// 2011/08/08: [jDG] Computes CNS during deco planning ascent.
// 2011/11/24: [jDG] Slightly faster and better NDL computation.
// 2011/12/17: [mH]  Remove of the useless debug stuff
// 2012/02/24: [jDG] Remove missed stop bug.
// 2012/02/25: [jDG] Looking for a more stable LOW grad factor reference.
// 2012/09/10: [mH]  Fill char_O_deco_time_for_log for logbook write
// 2012/10/05: [jDG] Better gas_volumes accuracy (average depth, switch between stop).
// 2013/03/05: [jDG] Should vault low_depth too.
// 2013/03/05: [jDG] Wrobell remark: ascent_to_first_stop works better with finer steps (2sec).
// 2013/05/08: [jDG] A. Salm remark: NOAA tables for CNS are in ATA, not bar.
// 2013/12/21: [jDG] Fix CNS calculation in deco plan w/o marked gas switch
// 2014/06/16: [jDG] Fix Helium diluent. Fix volumes with many travel mix.
// 2014/06/29: [mH]  Compute int_O_ceiling
// 2015/06/12: [jDG] Fix NDL prediction while desaturating with the Buhlmann model.
// 2017/08/04: [mH]  Switch to absolute GF everywhere and apply safety margin parameters to both models (GF and non-GF), fixes from Ralph Lembcke
// 2017/10/31: [rl]  enhancements for pSCR mode and introduction of 2nd deco plan computation
// 2017/12/31: [rl]  completion of 2nd deco plan computation and various up-fixes
// 2018/02/17: [rl]  switch-over to new ceiling rounding (V2.98a)
//
//
// Literature:
// Buhlmann, Albert: Tauchmedizin; 4. Auflage [2002];
// Schr"oder, Kai & Reith, Steffen; 2000; S"attigungsvorg"ange beim Tauchen, das Modell ZH-L16, Funktionsweise von Tauchcomputern; http://www.achim-und-kai.de/kai/tausim/saett_faq
// Morrison, Stuart; 2000; DIY DECOMPRESSION; http://www.lizardland.co.uk/DIYDeco.html
// Balthasar, Steffen; Dekompressionstheorie I: Neo Haldane Modelle; http://www.txfreak.de/dekompressionstheorie_1.pdf
// Baker, Erik C.; Clearing Up The Confusion About "Deep Stops"
// Baker, Erik C.; Understanding M-values; http://www.txfreak.de/understanding_m-values.pdf
//
//

// *********************
// ** I N C L U D E S **
// *********************
#include <math.h>

// ***********************************************
// ** V A R I A B L E S   D E F I N I T I O N S **
// ***********************************************

#include	"p2_definitions.h"
#define		TEST_MAIN
#include	"shared_definitions.h"


// ambient pressure at different mountain heights
#define P_ambient_1000m					0.880	// [bar]  based on 990 hPa and 20°C at sea level, 15°C at altitude
#define P_ambient_2000m					0.782	// [bar]
#define P_ambient_3000m					0.695	// [bar]

// ambient pressure in aircraft cabin during flying - worst case according to Buhlmann
#define P_ambient_fly					0.600	// [bar], 0.600 bar is the value used by Buhlmann for his flying-after-diving calculations
												//        0.735 bar is a typical cabin pressure for nowadays commercial jet aircrafts
												//        -----
												//        0.135 bar safety margin

// constants and factors
#define ppWater							0.0627	// water vapor partial pressure in the lungs
#define METER_TO_BAR					0.09985	// conversion factor
#define BAR_TO_METER					10.0150	// conversion factor (1.0/METER_TO_BAR)
#define SURFACE_DESAT_FACTOR			0.7042	// surface desaturation safety factor
#define HYST							1.0E-06	// threshold for tissue graphics on-gassing / off-gassing visualization

// thresholds
#define GF_WARNING_THRESHOLD			100		// threshold for GF   warning (attention threshold is current GF_high)
#define CNS_WARNING_THRESHOLD			100		// threshold for CNS  warning
#define CNS_ATTENTION_THRESHOLD			 70		// threshold for CNS  attention
#define ppO2_ATTENTION_THRESHOLD		130		// threshold for ppO2 attention (thresholds for warnings come by options_table.asm)
#define ppO2_GAP_TO_SETPOINT			 10		// gap between setpoint and max. ppO2 of the pure diluent [cbar]
#define GAS_NEEDS_ATTENTION_THRESHOLD	0.70	// threshold for gas needs attention

// deco engine states and modes - char_O_deco_status
#define DECO_STATUS_MASK				0x03
#define DECO_STATUS_START				0x00
#define DECO_STATUS_FINISHED			0x00
#define DECO_STATUS_STOPS				0x01 
#define DECO_STATUS_RESULTS				0x02
#define DECO_STATUS_INIT				0x03

#define DECO_MODE_MASK					0x0C
#define DECO_MODE_LOOP					0x04
#define DECO_MODE_CCR					0x04	// to be used with == operator in combination with DECO_MODE_MASK only!
#define DECO_MODE_PSCR					0x08

#define DECO_PLAN_ALTERNATE				0x10
#define DECO_CNS_CALCULATE				0x20
#define DECO_VOLUME_CALCULATE			0x40
#define DECO_ASCENT_DELAYED				0x80

// deco engine states and modes - char_O_main_status
//#define DECO_MODE_MASK				0x0C
//#define DECO_MODE_LOOP				0x04
//#define DECO_MODE_CCR					0x04	// to be used with == operator in combination with DECO_MODE_MASK only!
//#define DECO_MODE_PSCR				0x08
#define DECO_GASCHANGE_OVRD				0x10
#define DECO_BOTTOM_CALCULATE			0x40

// deco engine states and modes - tissue_increment
#define TIME_MASK						0x7F	// (127 decimal, bits 0-6 set)
#define TISSUE_FLAG						0x80	// (128 decimal, bit   7  set)

// deco engine warnings
#define	DECO_WARNING_IBCD				0x01
#define	DECO_WARNING_IBCD_lock			0x02
#define	DECO_WARNING_MBUBBLES			0x04
#define	DECO_WARNING_MBUBBLES_lock		0x08
#define	DECO_WARNING_OUTSIDE			0x10
#define	DECO_WARNING_OUTSIDE_lock		0x20
#define DECO_WARNING_STOPTABLE_OVERFLOW	0x40
#define DECO_FLAG						0x80

// flags used with integer numbers
#define INT_FLAG_INVALID				0x0400
#define INT_FLAG_ZERO					0x0800
#define INT_FLAG_LOW					0x1000
#define	INT_FLAG_HIGH					0x2000
#define INT_FLAG_ATTENTION				0x4000
#define	INT_FLAG_WARNING				0x8000



// *************************
// ** P R O T O T Y P E S **
// *************************

static void calc_hauptroutine(void);
static void calc_hauptroutine_data_input(void);
static void calc_hauptroutine_calc_deco(void);
static void calc_alveolar_pressures(void);
static void calc_tissues(void);
static void calc_NDL_time(void);
static void calc_ascenttime(void);
static void calc_CNS_increment(void);
static void calc_desaturation_time(void);
static void calc_ascent_to_first_stop(void);
static void calc_limit(PARAMETER float GF_current);
static void calc_interval(PARAMETER unsigned char time_increment);

static void gas_find_current(void);
static void gas_set_ratios(void);
static void convert_CNS_for_display(void);
static void convert_sim_CNS_for_display(void);
static void publish_deco_table(void);
static void clear_deco_table(void);
static void clear_tissue(void);

static unsigned char calc_nextdecodepth(void);
static unsigned char gas_find_better(void);
static unsigned char update_deco_table(PARAMETER unsigned char time_increment);


//---- Bank 5 parameters -----------------------------------------------------
#ifndef UNIX
#   pragma udata bank5=0x500
#endif

// general deco parameters

static float			GF_low;							// initialized from deco parameters, constant during all computations
static float			GF_high;						// initialized from deco parameters, constant during all computations
static float			GF_delta;						// initialized from deco parameters, constant during all computations
static float			locked_GF_step_norm;			// GF_delta / low_depth_norm in normal plan
static float			locked_GF_step_alt;				// GF_delta / low_depth_alt  in alternative plan

static float			low_depth_norm;					// Depth of deepest stop in normal plan
static float			low_depth_alt;					// Depth of deepest stop in alternative plan

static float			float_ascent_speed;				// ascent speed from options_table (1.0 .. 10.0 m/min)
static float			float_deco_distance;			// additional depth below stop depth for tissue, CNS and gas volume calculation
static float			float_saturation_multiplier;	// safety factor for on-gassing rates
static float			float_desaturation_multiplier;	// safety factor for off-gassing rates

// real context: what we are doing now

static float			ceiling;						// minimum tolerated relative pressure (i.e. without surface pressue)
static float			CNS_fraction;					// current CNS (1.00 = 100%)

static unsigned short	deco_tissue_vector;				// 16 bit vector to memories all tissues that are in decompression
static unsigned short	IBCD_tissue_vector;				// 16 bit vector to memories all tissues that experience IBCD

// simulation context: used to predict ascent

static float			sim_ceiling;					// minimum tolerated relative pressure (i.e. without surface pressue)
static float			sim_CNS_fraction;				// CNS increase during predicted ascent, 0.01 = 1%

static unsigned int		int_sim_CNS_fraction;			// CNS increase during predicted ascent, in %

static unsigned char	sim_depth_limit;				// depth of next stop in meters, used in deco calculations
static unsigned char	split_N2_He[NUM_COMP];			// used for calculating the desaturation time
static unsigned char	NDL_lead_tissue;				// used to cache tissue to start with calculating NDL


// stops table

static unsigned char	internal_deco_depth[NUM_STOPS];	// depth of the stop
static unsigned char	internal_deco_time[NUM_STOPS];	// duration of the stop
static unsigned char	internal_deco_gas[NUM_STOPS];	// gas used at the stop


// transfer variables between calc_desaturation_time() and calc_desaturation_time_helper()

static float			desat_factor;					// used to cache a pre-computed factor
static float			var_ht;							// buffer for a half-time factor
static float			pres_target;					// target pressure for a compartment
static float			pres_actual;					// current pressure of the compartment
static unsigned int		int_time;						// time it takes for the compartment to reach the target pressure


// transfer variables between gas_volumes() and gas_volumes_helper()

static float			float_depth;					// depth of the stop or half-way point
static float			float_time;						// duration of the stop or ascent phase
static float			volume;							// computed volume of gas
static unsigned char	usage;							// gas usage in l/min


// auxiliary variables for data buffering

static float			N2_equilibrium;					// used for N2 tissue graphics scaling
static float			temp_tissue;					// auxiliary variable to buffer tissue pressures
static float			float_pSCR_factor;				// pre-computed factor for pSCR ppO2 drop calculation


// 35 byte free space left in this bank (4 bytes per float, 2 bytes per int/short, 1 byte per char)


//---- Bank 6 parameters -----------------------------------------------------
#ifndef UNIX
#   pragma udata bank6=0x600
#endif

// indexing and sequencing

static unsigned char	ci;								// used as index to the Buhlmann tables
static unsigned char	twosectimer = 0;				// used for timing the tissue updating
static unsigned char	tissue_increment;				// selector for real/simulated tissues and time increment


// environmental and gas data

static float			pres_surface;					// absolute pressure at the surface

static unsigned char	bottom_depth;					// bottom depth in meters, used by CNS and gas needs calculation

static float			pres_respiration;				// current depth in absolute pressure
static float			O2_ratio;						// real breathed gas oxygen ratio
static float			N2_ratio;						// real breathed gas nitrogen ratio
static float			He_ratio;						// real breathed gas helium ratio
static float			pSCR_drop;						// real ppO2 drop in pSCR loop

static float			sim_pres_respiration;			// simulated current depth in abs.pressure, used for deco calculations
static float			sim_O2_ratio;					// simulated breathed gas oxygen ratio
static float			sim_N2_ratio;					// simulated breathed gas nitrogen ratio
static float			sim_He_ratio;					// simulated breathed gas helium ratio
static float			sim_pSCR_drop;					// simulated ppO2 drop in pSCR loop

static float			O2_ppO2;						// ppO2 - calculated for pure oxygen at current depth
static float			OC_ppO2;						// ppO2 - calculated for breathed in OC mode
static float			pSCR_ppO2;						// ppO2 - calculated for breathed from pSCR loop

static float			ppO2;							// partial pressure of breathed oxygen
static float			ppN2;							// partial pressure of breathed nitrogen
static float			ppHe;							// partial pressure of breathed helium


// Result values from calculation functions

static float			CNS_fraction_inc;				// increment of CNS load, 0.01 = 1%

static unsigned char	char_ppO2;						// partial pressure of breathed oxygen, as integer 100 = 1.00 bar
static unsigned char	NDL_time;						// time in minutes until reaching NDL
static unsigned int		ascent_time;					// time in minutes needed for the ascent


// Buhlmann model parameters

static float			var_N2_a;						// Buhlmann a, for current N2 tissue
static float			var_N2_b;						// Buhlmann b, for current N2 tissue
static float			var_He_a;						// Buhlmann a, for current He tissue
static float			var_He_b;						// Buhlmann b, for current He tissue
static float			var_N2_e;						// exposition, for current N2 tissue
static float			var_He_e;						// exposition, for current He tissue
static float			var_N2_ht;						// half-time for current N2 tissue
static float			var_He_ht;						// half-time for current N2 tissue


// Gas switch history

static unsigned char	sim_gas_first_used;				// Number of first used gas, for bottom segment
static unsigned char	sim_gas_last_used;				// number of last  used gas
static unsigned char	sim_gas_last_depth;				// change depth of last used gas


// Vault to back-up & restore tissue data

static float			pres_tissue_N2_vault[NUM_COMP];	// stores the nitrogen tissue pressures
static float			pres_tissue_He_vault[NUM_COMP];	// stores the helium tissue pressures
static float			cns_vault_float;				// stores current CNS (float representation)
static unsigned char	deco_warnings_vault;			// stores warnings status


// 8 byte free space left in this bank (4 bytes per float, 2 bytes per int/short, 1 byte per char)


//---- Bank 7 parameters -----------------------------------------------------
#ifndef UNIX
#   pragma udata bank7=0x700
#endif

// Keep order and position of the variables in bank 7 as they are backed-up to & restored from EEPROM 

float					pres_tissue_N2[NUM_COMP];		// 16 floats = 64 bytes
float					pres_tissue_He[NUM_COMP];		// 16 floats = 64 bytes

float					sim_pres_tissue_N2[NUM_COMP];	// 16 floats = 64 bytes
float					sim_pres_tissue_He[NUM_COMP];	// 16 floats = 64 bytes

// bank is full!


//---- Bank 8 parameters -----------------------------------------------------
#ifndef UNIX
#   pragma udata overlay bank8=0x800

static char				md_pi_subst[256];				// Overlay C-code data stack here, too.

#   define C_STACK md_pi_subst
#endif


// Back to bank6 for further tmp data
// Do not delete this assignment, it is needed by the compiler/linker.
#ifndef UNIX
#   pragma udata bank6
#endif


//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
////////////////              THE LOOKUP TABLES               ////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

#ifndef UNIX
#   pragma romdata Buhlmann_tables = 0x1DD00  // Needs to be in UPPER bank.
#endif

rom const float Buhlmann_ab[4*16] = {
// Data ZH-L16C, from Bühlmann Tauchmedizin 2002, option 1a (4mn)
// a for N2    b for N2     a of He     b for He
	1.2599,     0.5050,     1.7424,     0.4245,
	1.0000,     0.6514,     1.3830,     0.5747,
	0.8618,     0.7222,     1.1919,     0.6527,
	0.7562,     0.7825,     1.0458,     0.7223,
	0.6200,     0.8126,     0.9220,     0.7582,
	0.5043,     0.8434,     0.8205,     0.7957,
	0.4410,     0.8693,     0.7305,     0.8279,
	0.4000,     0.8910,     0.6502,     0.8553,
	0.3750,     0.9092,     0.5950,     0.8757,
	0.3500,     0.9222,     0.5545,     0.8903,
	0.3295,     0.9319,     0.5333,     0.8997,
	0.3065,     0.9403,     0.5189,     0.9073,
	0.2835,     0.9477,     0.5181,     0.9122,
	0.2610,     0.9544,     0.5176,     0.9171,
	0.2480,     0.9602,     0.5172,     0.9217,
	0.2327,     0.9653,     0.5119,     0.9267
};

rom const float Buhlmann_ht[2*16] = {
// Compartment half-life, in minute
//--- N2 ---- He ----------------------
	  4.0,    1.51,
	  8.0,    3.02,
	 12.5,    4.72,
	 18.5,    6.99,
	 27.0,   10.21,
	 38.3,   14.48,
	 54.3,   20.53,
	 77.0,   29.11,
	109.0,   41.20,
	146.0,   55.19,
	187.0,   70.69,
	239.0,   90.34,
	305.0,  115.29,
	390.0,  147.42,
	498.0,  188.24,
	635.0,  240.03
};

rom const float e2secs[2*16] = {
// result of  1 - 2^(-1/(2sec*HT))
//---- N2 ------------- He ------------
	5.75958E-03,    1.51848E-02,  
	2.88395E-03,    7.62144E-03,
	1.84669E-03,    4.88315E-03,
	1.24813E-03,    3.29997E-03,
	8.55371E-04,    2.26041E-03,
	6.03079E-04,    1.59437E-03,
	4.25414E-04,    1.12479E-03,
	3.00019E-04,    7.93395E-04,
	2.11949E-04,    5.60641E-04,
	1.58240E-04,    4.18555E-04,
	1.23548E-04,    3.26795E-04,
	9.66686E-05,    2.55722E-04,
	7.57509E-05,    2.00387E-04,
	5.92416E-05,    1.56716E-04,
	4.63943E-05,    1.22734E-04,
	3.63850E-05,    9.62538E-05
//-------------------------------------
};

rom const float e1min[2*16] = {
// Integration constant for 1 minute,
// Ie. 1- 2^(-1/HT)
//----- N2 --------- e 1min He --------
	1.59104E-01,    3.68109E-01,
	8.29960E-02,    2.05084E-01,
	5.39424E-02,    1.36579E-01,
	3.67742E-02,    9.44046E-02,
	2.53454E-02,    6.56359E-02,
	1.79351E-02,    4.67416E-02,
	1.26840E-02,    3.31991E-02,
	8.96152E-03,    2.35301E-02,
	6.33897E-03,    1.66832E-02,
	4.73633E-03,    1.24808E-02,
	3.69981E-03,    9.75753E-03,
	2.89600E-03,    7.64329E-03,
	2.27003E-03,    5.99417E-03,
	1.77572E-03,    4.69082E-03,
	1.39089E-03,    3.67548E-03,
	1.09097E-03,    2.88359E-03
//-------------------------------------
};

rom const float e10min[2*16] = {
// The 10 min Value in float notation:
//  result of 1 - 2^(-10/ht)
//---- N2 -------------- He -----------
	8.23223E-01,    9.89851E-01,
	5.79552E-01,    8.99258E-01,
	4.25651E-01,    7.69737E-01,
	3.12487E-01,    6.29027E-01,
	2.26416E-01,    4.92821E-01,
	1.65547E-01,    3.80407E-01,
	1.19840E-01,    2.86538E-01,
	8.60863E-02,    2.11886E-01,
	6.16117E-02,    1.54849E-01,
	4.63665E-02,    1.18026E-01,
	3.63881E-02,    9.34005E-02,
	2.85855E-02,    7.38569E-02,
	2.24698E-02,    5.83504E-02,
	1.76160E-02,    4.59303E-02,
	1.38222E-02,    3.61528E-02,
	1.08563E-02,    2.84646E-02
//-------------------------------------
};

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
////////////////               THE SUBROUTINES                ////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// all new in v.102
// moved from 0x0D000 to 0x0C000 in v.108
#ifndef UNIX
#	pragma code p2_deco = 0x0C000
#endif

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
////////////////             U T I L I T I E S                ////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// Bump to blue-screen when an assert is wrong
#ifdef __DEBUG
void assert_failed(PARAMETER short int line)
{
}
#endif

//////////////////////////////////////////////////////////////////////////////
// When calling C code from ASM context, the data stack pointer and
// frames should be reset. Bank8 is used by stack

#ifdef CROSS_COMPILE
#	define RESET_C_STACK
#else
#	ifdef __DEBUG
#		define RESET_C_STACK fillDataStack();
		void fillDataStack(void)
		{
			_asm
				LFSR    1,C_STACK
				MOVLW   0xCC
		loop:	MOVWF   POSTINC1,0
				TSTFSZ  FSR1L,0
				BRA     loop

				LFSR    1,C_STACK
				LFSR    2,C_STACK
			_endasm
		}
#	else
#		define	RESET_C_STACK	\
		_asm					\
			LFSR	1, C_STACK	\
			LFSR	2, C_STACK	\
		_endasm
#	endif
#endif

//////////////////////////////////////////////////////////////////////////////
// Fast subroutine to read timer 5.
// Note: result is in 1/32 of milliseconds (30,51757813 us/bit to be precise)
static unsigned short tmr5(void)
{
#ifndef CROSS_COMPILE
	_asm
		movff	0xf7c,PRODL		// TMR5L
		movff	0xf7d,PRODH		// TMR5H
	_endasm						// result in PRODH:PRODL.
#else
	return 0;
#endif
}

//////////////////////////////////////////////////////////////////////////////
// read Buhlmann tables A and B for compartment ci
//
static void read_Buhlmann_coefficients(void)
{
#ifndef CROSS_COMPILE
	// Note: we don't use far rom pointer, because the
	//       24 bits is too complex, hence we have to set
	//       the UPPER page ourself...
	//       --> Set zero if tables are moved to lower pages !
	_asm
		movlw 1
		movwf TBLPTRU,0
	_endasm
#endif

	assert( ci < NUM_COMP );

	// Use an interleaved array (AoS) to access coefficients with a
	// single addressing.
	{
		overlay rom const float* ptr = &Buhlmann_ab[4*ci];
		var_N2_a = *ptr++;
		var_N2_b = *ptr++;
		var_He_a = *ptr++;
		var_He_b = *ptr++;
	}
}

//////////////////////////////////////////////////////////////////////////////
// read Buhlmann tables for compartment ci
// If period == 0 : 2sec interval
//              1 : 1 min interval
//              2 : 10 min interval.
static void read_Buhlmann_times(PARAMETER char period)
{
#ifndef CROSS_COMPILE
	// Note: we don't use far rom pointer, because the
	//       24 bits is to complex, hence we have to set
	//       the UPPER page ourself...
	//       --> Set zero if tables are moved to lower pages!
	_asm
		movlw 1
		movwf TBLPTRU,0
	_endasm
#endif

	assert( ci < NUM_COMP );

	// Integration intervals
	switch(period)
	{
	case 0: //---- 2 sec -----------------------------------------------------
		{
			overlay rom const float* ptr = &e2secs[2*ci];
			var_N2_e = *ptr++;
			var_He_e = *ptr++;
		}
		break;

	case 1: //---- 1 min -----------------------------------------------------
		{
			overlay rom const float* ptr = &e1min[2*ci];
			var_N2_e = *ptr++;
			var_He_e = *ptr++;
		}
		break;

	case 2: //---- 10 min ----------------------------------------------------
		{
			overlay rom const float* ptr = &e10min[2*ci];
			var_N2_e = *ptr++;
			var_He_e = *ptr++;
		}
	break;

	default:
		assert(0);  // Never go there...
	}
}

//////////////////////////////////////////////////////////////////////////////
// read Buhlmann tables for compartment ci
//
static void read_Buhlmann_ht(void)
{

#ifndef CROSS_COMPILE
	// Note: we don't use far rom pointer, because the
	//       24 bits is to complex, hence we have to set
	//       the UPPER page ourself...
	//       --> Set zero if tables are moved to lower pages !
	_asm
		movlw 1
		movwf TBLPTRU,0
	_endasm
#endif

	assert( ci < NUM_COMP );
	{
		overlay rom const float* ptr = &Buhlmann_ht[2*ci];
		var_N2_ht = *ptr++;
		var_He_ht = *ptr++;
	}

	assert( 4.0    <= var_N2_ht && var_N2_ht <= 635.0 );
	assert( 1.5099 <= var_He_ht && var_He_ht <= 240.03 );
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
////////////////      THE JUMP-IN CODE for the asm code       ////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// deco_calc_hauptroutine
//
// called from: divemode.asm
//
// Called every second during diving,
// updates tissues on every second invocation.
//
// Every few seconds (or slower when TTS > 16):
// - Updates deco table (char_O_deco_time/depth) with new values,
// - updates ascent time, and
// - sets status to zero (so we can check there is new results).
//
void deco_calc_hauptroutine(void)
{
	RESET_C_STACK
	calc_hauptroutine();
}

//////////////////////////////////////////////////////////////////////////////
// deco_clear_tissue
//
// called from: start.asm
//              menu_tree.asm
//              simulator.asm
//
// Sets all tissues to equilibrium with Air at ambient pressure,
// resets all CNS values, any warnings and resets all model output.
//
void deco_clear_tissue(void)
{
	RESET_C_STACK
	clear_tissue();
}

//////////////////////////////////////////////////////////////////////////////
// deco_calc_dive_interval
//
// called from: simulator.asm
//
// Updates tissues and CNS value for char_I_dive_interval minutes on Air
// at ambient pressure and calculates resulting GF factor and ceiling for
// a GF-high of 100% (ceiling and GF factor not used by simulator.asm)
//
void deco_calc_dive_interval(void)
{
	RESET_C_STACK
	calc_interval(char_I_dive_interval);
}

//////////////////////////////////////////////////////////////////////////////
// deco_calc_dive_interval_1min
//
// called from: start.asm
//              sleepmode.asm
//              surfmode.asm
//              menu_tree.asm
//              ghostwriter.asm
//
// Updates tissues and CNS value for 1 minute on Air at ambient pressure and
// calculates resulting GF factor and ceiling for a GF-high of 100% (ceiling
// is not used by *.asm files).
//
void deco_calc_dive_interval_1min(void)
{
	RESET_C_STACK
	calc_interval(1);
}


//////////////////////////////////////////////////////////////////////////////
// deco_calc_dive_interval_1min
//
// called from: sleepmode.asm
//
// Updates tissues and CNS value for 10 minutes on Air at ambient pressure and
// calculates resulting GF factor and ceiling for a GF-high of 100% (ceiling
// is not used by sleepmode.asm).
//
void deco_calc_dive_interval_10min(void)
{
	RESET_C_STACK
	calc_interval(10);
}


//////////////////////////////////////////////////////////////////////////////
// deco_calc_desaturation_time
//
// called from: start.asm
//              surfmode.asm
//              menu_tree.asm
//              ghostwriter.asm
//
// Computes desaturation and no-fly times.
//
void deco_calc_desaturation_time(void)
{
	RESET_C_STACK
	calc_desaturation_time();
}

//////////////////////////////////////////////////////////////////////////////
// deco_push_tissues_to_vault
//
// called from: simulator.asm
//
// Makes a backup of the state of the real tissues and the deco engine.
//
void deco_push_tissues_to_vault(void)
{
	RESET_C_STACK
	push_tissues_to_vault();
}

//////////////////////////////////////////////////////////////////////////////
// deco_pull_tissues_from_vault
//
// called from: simulator.asm
//              ghostwriter.asm
//
// Restores the state of the real tissues and the deco engine from the backup.
//
void deco_pull_tissues_from_vault(void)
{
	RESET_C_STACK
	pull_tissues_from_vault();
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
////////////////                THE FUNCTIONS                 ////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////////
// calc_nextdecodepth
//
// new in v.102
//
// INPUT, changing during dive:
//      sim_pres_respiration : current depth in absolute pressure
//
// INPUT, fixed during dive:
//      pres_surface
//      GF_delta
//      GF_high
//      GF_low
//      char_I_depth_last_deco
//
// MODIFIED
//      locked_GF_step_norm/_alt : used for GF model
//      low_depth_norm/_alt      : used for GF model
//
// OUTPUT
//      sim_depth_limit : depth of next stop in meters        (if RETURN == true )
//                        depth we can ascent to without stop (if RETURN == false)
//
// RETURN TRUE if a stop is needed.
//
static unsigned char calc_nextdecodepth(void)
{
	overlay unsigned char need_stop;

	// compute current depth in meters
	overlay float depth = (sim_pres_respiration - pres_surface) * BAR_TO_METER;

	// compute depth in meters after 1 minute of ascent at float_ascent_speed (5..10 m/min)
	overlay float min_depth = (depth > float_ascent_speed) ? (depth - float_ascent_speed) : 0.0;


	// allow for 200mbar of weather dependent surface pressure change
	assert( depth >= -0.2 );


	//---- check if a stop is needed for deco reasons ----------------------------

	// switch on deco model
	if( char_I_deco_model != 0 )
	{
		//---- ZH-L16 + GRADIENT FACTOR Model ------------------------------------

		overlay float locked_GF_step;
		overlay float low_depth;
		overlay float limit_depth;

		overlay unsigned char first_stop = 0;


		// calculate minimum depth we can ascent to in bar relative pressure
		calc_limit(GF_low);

		// check if we can surface directly
		if( sim_ceiling <= 0.0 )
		{
			min_depth = 0.0;		// set minimum depth to 0 meters = surface
			goto no_deco_stop;		// done.
		}

		// convert minimum depth we can ascent to from relative pressure to depth in meters
		limit_depth = sim_ceiling * BAR_TO_METER;

		// recall low_depth dependent on current plan
		low_depth = (char_O_deco_status & DECO_PLAN_ALTERNATE) ? low_depth_alt : low_depth_norm;
		
		// Store the deepest point needing a deco stop as the LOW reference for GF.
		// NOTE: following stops will be validated using this LOW-HIGH GF scale,
		//       so if we want to keep coherency, we should not validate this stop
		//       yet, but apply the search to it, as for all the following stops afterward.
		if( limit_depth > low_depth )
		{
			// update GF parameters
			low_depth      = limit_depth;
			locked_GF_step = GF_delta / low_depth;

			// store updated GF parameters dependent on current plan
			if( char_O_deco_status & DECO_PLAN_ALTERNATE )
			{
				low_depth_alt       = low_depth;
				locked_GF_step_alt  = locked_GF_step;
			}
			else
			{
				low_depth_norm      = low_depth;
				locked_GF_step_norm = locked_GF_step;
			}
		}
		else
		{
			// recall locked_GF_step dependent on current plan
			locked_GF_step = (char_O_deco_status & DECO_PLAN_ALTERNATE) ? locked_GF_step_alt : locked_GF_step_norm;
		}

		// invalidate this stop if we can ascent for 1 minute without going above minimum required deco depth
		if( limit_depth < min_depth ) goto no_deco_stop;


		// if program execution passes here, we need a deco stop

		// Round to multiple of 3 meters
		first_stop = 3 * (unsigned char)(0.9995 + limit_depth * 0.333333);

		// check a constraint
		assert( first_stop < 128 );

		// apply correction for the shallowest stop, use char_I_depth_last_deco (3..6 m) instead
		if( first_stop == 3 ) first_stop = char_I_depth_last_deco;

		// We have a stop candidate.
		// But maybe ascending to the next stop will diminish the constraint,
		// because the GF might decrease more than the pressure gradient...
		while(first_stop > 0)
		{
			// Next depth
			overlay unsigned char next_stop;

			// invalidate this stop if we can ascent one more minute without going above minimum required deco depth
			if( first_stop <= (unsigned char)min_depth ) goto no_deco_stop;

			// compute depth of next stop
			if      ( first_stop <= char_I_depth_last_deco ) next_stop = 0;
			else if ( first_stop == 6                      ) next_stop = char_I_depth_last_deco;
			else                                             next_stop = first_stop - 3;

			// compute limit with the GF of the new stop candidate
			if( (low_depth == 0.0) || (next_stop > low_depth) ) calc_limit(GF_low);
			else                                                calc_limit(GF_high - next_stop * locked_GF_step);

			// check if ascent to the next stop candidate is possible
			if( sim_ceiling * BAR_TO_METER >= next_stop ) goto deco_stop_found;	// no - ascent to next_stop forbidden

			// else, validate that stop and loop...
			first_stop = next_stop;
		}

no_deco_stop:
		need_stop       = 0;							// set flag for stop needed to 'no'
		sim_depth_limit = (unsigned char)min_depth;		// report depth we can ascent to without stop
		goto done;

deco_stop_found:
		need_stop       = 1;							// set flag for stop needed to 'yes'
		sim_depth_limit = (unsigned char)first_stop;	// stop depth, in meters

done:
		;
	}
	else
	{
		//---- ZH-L16 model -------------------------------------------------

		overlay float limit_depth;


		// calculate minimum depth we can ascent to in bar relative pressure
		calc_limit(1.0);

		// check if we can surface directly
		if (sim_ceiling >= 0)
		{
			// no - set flag for stop needed to 'yes'
			need_stop = 1;

			// convert stop depth in relative pressure to stop index
			limit_depth = sim_ceiling * BAR_TO_METER / 3;

			// convert stop index to depth in meters, rounded to multiple of 3 meters
			sim_depth_limit = 3 * (short)(limit_depth + 0.99);

			// correct last stop to 4m/5m/6m
			if( sim_depth_limit == 3 ) sim_depth_limit = char_I_depth_last_deco;
		}
		else
		{
			// yes - set flag for stop needed to 'no'
			need_stop       = 0;
			
			// set depth we can ascent to as 0 = surface
			sim_depth_limit = 0;
		}
	}

	// After the first deco stop, gas changes are only done at deco stops now!

	// check if a stop is found and there is a better gas to switch to
	if( need_stop && gas_find_better() )
	{
		// set the new calculation ratios for N2, He and O2
		gas_set_ratios();

		// prime the deco stop with the gas change time
		update_deco_table(char_I_gas_change_time); 
	}

	return need_stop;
}

//////////////////////////////////////////////////////////////////////////////
// publish_deco_table
//
// Buffer the stops, once computed, so we can continue to display them
// while computing the next set.
//
static void publish_deco_table(void)
{
	overlay unsigned char x, y;


	// Copy depth of the first (deepest) stop, because when reversing
	// order, it will be hard to find...
	char_O_first_deco_depth = internal_deco_depth[0];
	char_O_first_deco_time  = internal_deco_time [0];

	for(x=0; x<NUM_STOPS; x++)
	{
		char_O_deco_depth[x] = internal_deco_depth[x];
		char_O_deco_time [x] = internal_deco_time [x];
		char_O_deco_gas  [x] = internal_deco_gas  [x];
	}

	//Now fill the char_O_deco_time_for_log array
	//---- First: search the first non-null depth
	for(x=(NUM_STOPS-1); x != 0; --x)
		if( internal_deco_depth[x] != 0 ) break;

	//---- Second: copy to output table (in reverse order)
	for(y=0; y<NUM_STOPS; y++, --x)
	{
		char_O_deco_time_for_log[y] = internal_deco_time [x];

		// Stop only once the last transfer is done.
		if( x == 0 ) break;
	}

	//---- Third: fill table with null until end
	for(y++; y<NUM_STOPS; y++)
		char_O_deco_time_for_log[y] = 0;
}

//////////////////////////////////////////////////////////////////////////////
// temp_tissue_safety
//
// outsourced in v.102
//
// Apply safety factors for both ZH-L16 models.
//
static void temp_tissue_safety(void)
{
	assert( 0.0 <  float_desaturation_multiplier && float_desaturation_multiplier <= 1.0 );
	assert( 1.0 <= float_saturation_multiplier   && float_saturation_multiplier   <= 2.0 );

	if( temp_tissue < 0.0 ) temp_tissue *= float_desaturation_multiplier;
	else                    temp_tissue *= float_saturation_multiplier;
}



//////////////////////////////////////////////////////////////////////////////
// Find current gas in the list (if any) and get its change depth
//
// Input:  char_I_current_gas : 1..5 or 6
//
// Output: sim_gas_last_used  : 1..6 or 0 if it is the gas set as FIRST
//         sim_gas_last_depth : change depth in meters or 0 if it is the gas set as FIRST
//
static void gas_find_current(void)
{
	assert( 1 <= char_I_current_gas && char_I_current_gas <= 6 );

	if( char_I_current_gas <= NUM_GAS )					// Gas 1-5
	{
		sim_gas_last_used  = sim_gas_first_used = char_I_current_gas;
		sim_gas_last_depth = char_I_deco_gas_change[sim_gas_last_used-1];	// > 0 for OC deco gases,
																			// > 0 for first & normal diluents,
																			// = 0 else
	}
	else
	{
		sim_gas_last_used  = sim_gas_first_used = 0;		// Gas 6 (the manually set one) has number 0 here
		sim_gas_last_depth                      = 0;		// handle it as a travel/normal gas
	}
}


//////////////////////////////////////////////////////////////////////////////
// Find the deco gas with the shallowest change depth beyond current depth
//
// INPUT   sim_depth_limit          : current depth in meters
//         char_I_deco_gas_change[] : change depths of the deco gases
//         sim_gas_last_depth       : change depth of the currently used gas, 0 if on the gas set as FIRST
//
// OUTPUT  sim_gas_last_depth       : switch depth            - only if return value is true
//         sim_gas_last_used        : index of the gas (1..5) - only if return value is true
//
// RETURNS TRUE if a better gas is available
//
static unsigned char gas_find_better(void)
{
	overlay unsigned char switch_depth = 255;
	overlay unsigned char switch_gas   = 0;
	overlay unsigned char j;


	// no automatic gas changes in CCR mode and - as of now - in pSCR mode
	if( char_O_deco_status & DECO_MODE_LOOP ) return 0;

	// Loop over all deco gases to find the shallowest one below or at current depth.
	for(j=0; j<NUM_GAS; ++j)
	{
		// Is this the gas we are already breathing?
		// If yes, skip this gas.
		if( j+1 == sim_gas_last_used ) continue;

		// Is the change depth of the gas shallower than the current depth?
		// If yes, skip this gas as it is not to be used yet.
		// Remark: this check will also skip all disabled gases and the gas set
		//         as 'first' because these have their change depth set to 0.
		if( sim_depth_limit > char_I_deco_gas_change[j] ) continue;

		// Is the change depth of the gas deeper than the change depth of the
		// gas we are currently one?
		// If yes, skip this gas as it is not better than the current one.
		// Remark: if there is more than one gas with the same change depth,
		//         the last one from the list will be taken.
		if( sim_gas_last_depth && (char_I_deco_gas_change[j] > sim_gas_last_depth) ) continue;

		// Is the change depth of the gas shallower or equal to the change depth
		// of the best gas found so far, or is it the first better gas found?
		// If yes, we have a better gas
		if( char_I_deco_gas_change[j] <= switch_depth )
		{
			switch_gas   = j+1;							// remember this gas (1..5)
			switch_depth = char_I_deco_gas_change[j];	// remember its change depth
		}
	}	// continue looping through all gases to eventually find an even better gas

	// has a better gas been found?
	if( switch_gas )
	{
		// yes
		sim_gas_last_used  = switch_gas;				// report the index of the better
		sim_gas_last_depth = switch_depth;				// report its change depth

		assert( sim_gas_last_depth < switch_depth );

		return 1;										// signal a better gas was found
	}
	else
	{
		return 0;										// signal no better gas was found
	}
}

//////////////////////////////////////////////////////////////////////////////
// Set calc_N2/He/O2_ratios by sim_gas_last_used
//
// Input:  sim_gas_last_used          : index of gas to use
//         N2_ratio, He_ratio         : if gas =    0 (the manually set gas)
//         char_I_deco_O2/He_ratio[]  : if gas = 1..5 (the configured gases)
//
// Output: sim_N2_ratio, sim_He_ratio : ratios of the inert gases
//         sim_pSCR_drop              : ppO2 drop in pSCR loop
//
static void gas_set_ratios(void)
{
	overlay float sim_IG_ratio;

	assert( 0 <= sim_gas_last_used <= NUM_GAS );


	// get gas ratios
	if( sim_gas_last_used == 0 )
	{
		sim_O2_ratio = O2_ratio;
		sim_He_ratio = He_ratio;
	}
	else
	{
		sim_O2_ratio = 0.01 * char_I_deco_O2_ratio[sim_gas_last_used-1];
		sim_He_ratio = 0.01 * char_I_deco_He_ratio[sim_gas_last_used-1];
	}

	// inert gas ratio (local helper variable)
	sim_IG_ratio     = 1.00 - sim_O2_ratio;

	// N2 ratio
	sim_N2_ratio     = sim_IG_ratio - sim_He_ratio;

	// ppO2 drop in pSCR loop
	sim_pSCR_drop    = sim_IG_ratio * float_pSCR_factor;


	assert( 0.0 <=  sim_N2_ratio && sim_N2_ratio  <= 0.95 );
	assert( 0.0 <=  sim_He_ratio && sim_He_ratio  <= 0.95 );
	assert(        (sim_N2_ratio +  sim_He_ratio) <= 0.95 );
}

//////////////////////////////////////////////////////////////////////////////
// Compute respired ppN2 and ppHe
//
// Input:  tissue_increment       : selector for targeting simulated or real tissues
//         char_O_main_status     : breathing mode for real      tissues
//         char_O_deco_status     : breathing mode for simulated tissues
//         (sim_)O2_ratio         : (simulated) O2 ratio breathed
//         (sim_)N2_ratio         : (simulated) N2 ratio breathed
//         (sim_)He_ratio         : (simulated) He ratio breathed
//         (sim_)pres_respiration : (simulated) respiration pressure
//         char_I_const_ppO2      : ppO2 reported from sensors or setpoint
//         char_I_PSCR_drop       : pSCR parameter
//         char_I_PSCR_lungratio  : pSCR parameter
//         pres_surface           : surface pressure
//         float_deco_distance    : safety factor
//         ppWater                : water-vapor pressure inside respiratory tract
//
// Output: ppN2                   : respired N2 partial pressure
//         ppHe                   : respired He partial pressure
//         char_ppO2              : breathed ppO2 in %, to be used for CNS calculation
//
void calc_alveolar_pressures(void)
{
	overlay float pres_diluent;
	overlay float calc_O2_ratio;
	overlay float calc_N2_ratio;
	overlay float calc_He_ratio;
	overlay float calc_pSCR_drop;

	overlay unsigned char status;


	assert( 0.00 <= N2_ratio && N2_ratio <= 1.00 );
	assert( 0.00 <= He_ratio && He_ratio <= 1.00 );
	assert( (N2_ratio + He_ratio) <= 1.00 );
	assert( 0.800 < pres_respiration && pres_respiration < 14.0 );

	assert( 0.00 <= sim_N2_ratio && N2_ratio <= 1.00 );
	assert( 0.00 <= sim_He_ratio && He_ratio <= 1.00 );
	assert( (sim_N2_ratio + sim_He_ratio) <= 1.00 );
	assert( 0.800 < sim_pres_respiration && sim_pres_respiration < 14.0 );


	// get input data according to context
	if( tissue_increment & TISSUE_FLAG )
	{
		//---- real tissues -----------------------------------------------------------
		status         = char_O_main_status;
		pres_diluent   = pres_respiration;
		calc_pSCR_drop = pSCR_drop;

		calc_O2_ratio  = O2_ratio;
		calc_N2_ratio  = N2_ratio;
		calc_He_ratio  = He_ratio;
	}
	else
	{
		//---- simulated tissues ------------------------------------------------------
		status         = char_O_deco_status;
		pres_diluent   = sim_pres_respiration;
		calc_pSCR_drop = sim_pSCR_drop;

		calc_O2_ratio  = sim_O2_ratio;
		calc_N2_ratio  = sim_N2_ratio;
		calc_He_ratio  = sim_He_ratio;

		// take deco offset into account, but not at surface
		if( pres_diluent > pres_surface ) pres_diluent += float_deco_distance;
	}

	//---- OC, CCR and Bailout Mode Gas Calculations -----------------------------------

	// calculate ppO2 of pure oxygen
	O2_ppO2 = (pres_diluent - ppWater);

	// capture failure condition in case pres_respiration is < ppWater (should never happen...)
	if( O2_ppO2 < 0.0 ) O2_ppO2 = 0.0;

	// calculate ppO2 of the pure gas (diluent)
	OC_ppO2 = O2_ppO2 * calc_O2_ratio;

	// calculate pSCR ppO2
	pSCR_ppO2 = OC_ppO2 - calc_pSCR_drop;

	// capture failure condition in case pSCR_ppO2 becomes negative
	if( pSCR_ppO2 < 0.0 ) pSCR_ppO2 = 0.0;


	//---- Loop modes : adjust ppN2 and ppHe for change in ppO2 due to setpoint (CCR) or drop (pSCR) ---
	if( status & DECO_MODE_LOOP )
	{
		overlay float const_ppO2;

		// get the current sensor reading (CCR / pSCR if fitted) or the fixed setpoint (CCR) / a zero (pSCR)
		const_ppO2 = 0.01 * char_I_const_ppO2;

		// Limit the setpoint to the maximum physically possible ppO2. This prevents for
		// example calculating with a setpoint of 1.3 bar in only 2 meters of depth.
		// Additionally, if limiting occurs, the ppO2 can be further reduced to account
		// for residual inert gases by the user-adjustable setting char_I_cc_max_frac_o2.

		if( const_ppO2 > pres_diluent )		// no ppWater subtracted here to give some margin for
		{									// sensors delivering data a little bit over target
			const_ppO2 = 0.01 * char_I_cc_max_frac_o2 * (pres_diluent - ppWater);
		}

		// check which kind of loop we are on
		if( status & DECO_MODE_PSCR )
		{
			//---- pSCR Mode --------------------------------------------------------------------------

			// Use the sensor value if available, but only in real tissue context!
			// In all other cases use calculated ppO2.
			if( char_I_const_ppO2 && (tissue_increment & TISSUE_FLAG)) ppO2 = const_ppO2;
			else                                                       ppO2 = pSCR_ppO2;
		}
		else
		{
			//---- CCR Mode ---------------------------------------------------------------------------

			// derive breathed ppO2 from (char_I_)const_ppO2,
			// which holds sensor reading or fixed setpoint
			ppO2 = const_ppO2;
		}

		// adjust diluent pressure (ppN2 + ppHe) for change
		// in ppO2 due to setpoint (CCR) or drop (pSCR)
		pres_diluent -= const_ppO2;
		pres_diluent /= calc_N2_ratio + calc_He_ratio;

		// capture all failure conditions, including div/0
		// in case diluent is pure O2
		if( (pres_diluent < 0.0) || (calc_O2_ratio > 99.5) )
		{
			pres_diluent = 0.0;
			ppO2         = OC_ppO2;
		}
	}
	else
	{	//---- OC mode ---------------------------------------------------------------------------------

		// breathed ppO2 is ppO2 of pure gas
		ppO2 = OC_ppO2;
	}


	// derive char_ppO2 in [cbar], used for calculating CNS%
	if      ( ppO2 <  0.01  ) char_ppO2  =   0;
	else if ( ppO2 >= 2.545 ) char_ppO2  = 255;
	else                      char_ppO2  = (unsigned char)(100 * ppO2 + 0.5);


	//---- calculate ppN2 and ppHe ---------------------------------------------------------------------

	if( pres_diluent > ppWater )
	{
		ppN2 = calc_N2_ratio * (pres_diluent - ppWater);
		ppHe = calc_He_ratio * (pres_diluent - ppWater);
	}
	else
	{
		ppN2 = 0.0;
		ppHe = 0.0;
	}
}

//////////////////////////////////////////////////////////////////////////////
// clear_tissue
//
// optimized in v.101 (var_N2_a)
//
// preload tissues with standard pressure for the given ambient pressure.
//
static void clear_tissue(void)
{
	pres_respiration = 0.001  * int_I_pres_respiration;
	N2_equilibrium   = 0.7902 * (pres_respiration - ppWater);

	for(ci=0; ci<NUM_COMP; ci++)
	{
		// cycle through the 16 Buhlmann N2 tissues
		pres_tissue_N2[ci]              = N2_equilibrium;	// initialize data for "real" tissue
		char_O_tissue_N2_saturation[ci] = 11;				// initialize data for tissue graphics

		// cycle through the 16 Buhlmann He tissues
		pres_tissue_He[ci]              = 0.0;				// initialize data for "real" tissue
		char_O_tissue_He_saturation[ci] = 0;				// initialize data for tissue graphics
	}

	// reset CNS values
	CNS_fraction = 0.0;
	int_O_CNS_fraction = int_O_normal_CNS_fraction = int_O_alternate_CNS_fraction = 0;


	// reset any warnings
	char_O_deco_warnings		= 0;

	// reset some more vars to their defaults
	char_O_nullzeit				= 240;
	int_O_ascenttime			= 0;
	int_O_alternate_ascenttime	= 0;
	int_O_gradient_factor		= 0;
}


//////////////////////////////////////////////////////////////////////////////
// calc_hauptroutine
//
// this is the major code in dive mode calculates:
//      the tissues,
//      the bottom time,
//      and simulates the ascend with all deco stops.
//
static void calc_hauptroutine(void)
{
	overlay unsigned int int_ppO2_min;
	overlay unsigned int int_ppO2_max;
	overlay unsigned int int_ppO2_max_dil;
	overlay float        EAD;
	overlay float        END;


	//--- Set-up Part --------------------------------------------------------------------------------

	// twosectimer:
	// calc_hauptroutine is now invoked every second to speed up the deco planning.
	// Because the tissue and CNS calculations are based on a two seconds period, a
	// toggle-timer is used by the respective routines to skip every 2nd invocation.
	twosectimer = (twosectimer) ? 0 : 1;			// toggle the toggle-timer


	// set up normal tissue updating or "fast forward" updating for simulator sim+5' function
	// and deco calculator bottom time calculation
	if( char_I_sim_advance_time > 0 )
	{
		// configure char_I_sim_advance_time minutes of tissue updating
		tissue_increment = char_I_sim_advance_time	// given number of minutes, limited to 127
						 | TISSUE_FLAG;				// set flag for updating the "real" tissues & CNS

		char_I_sim_advance_time = 0;				// clear "mailbox"
	}
	else
	{
		// configure 2 seconds of tissue updating
		tissue_increment = 0						// encoding for 2 seconds update
						 | TISSUE_FLAG;				// set flag for updating the "real" tissues & CNS
	}


	//---- Calculations Part ----------------------------------------------------------------------

	// acquire current environment data
	calc_hauptroutine_data_input();

	// calculate ppN2 and ppHe
	calc_alveolar_pressures();

	// All deco code is invoked every second. But as the tissue and CNS updates are based
	// on 2 seconds periods, each update is done only on each 2nd second.
	// In case a "fast forward" of the tissues is commanded, the 2-seconds rule is over raided.
	// To distribute computational load, updating of tissues and CNS is done in alternation.
	if( twosectimer || (tissue_increment & TIME_MASK) )
	{
		// calculate the real tissues
		calc_tissues();

		// calculate ceiling (at GF_high) and current GF
		calc_limit(GF_high);
	}

	if( !twosectimer || (tissue_increment & TIME_MASK) )
	{
		// calculate CNS value increment for the real tissues
		calc_CNS_increment();

		// increment CNS value of the real tissues
		CNS_fraction += CNS_fraction_inc;

		// compute integer copy of CNS value for display purpose
		convert_CNS_for_display();
	}


	//---- Calculate and Export EAD and END ------------------------------------------------------

	// calculate EAD (Equivalent Air Depth): equivalent depth for the same N2 level with plain air
	EAD = (ppN2 / 0.7902 + ppWater - pres_surface) * BAR_TO_METER;

	// calculate END (Equivalent Narcotic Depth): here O2 is treated as narcotic, too
	// Source cited: The Physiology and Medicine of Diving by Peter Bennett and David Elliott,
	//               4th edition, 1993, W.B.Saunders Company Ltd, London.
	END = (pres_respiration - ppHe - pres_surface) * BAR_TO_METER;

	// export EAD
	if( (EAD < 0.0) || (EAD > 245.5) ) char_O_EAD = 0;
	else                               char_O_EAD = (unsigned char)(EAD + 0.5);

	// export END
	if( (END < 0.0) || (END > 245.5) ) char_O_END = 0;
	else                               char_O_END = (unsigned char)(END + 0.5);


	//---- Compute ppO2 Values in [cbar] ---------------------------------------------------------

	// pure oxygen ppO2
	if		( O2_ppO2       <  0.01  ) int_O_O2_ppO2       =   0;
	else if ( O2_ppO2       >= 9.995 ) int_O_O2_ppO2       = 999;
	else                               int_O_O2_ppO2       = (unsigned int)(100 *   O2_ppO2 + 0.5);
	
	// pure gas ppO2
	if      ( OC_ppO2       <  0.01  ) int_O_pure_ppO2     =   0;
	else if ( OC_ppO2       >= 9.995 ) int_O_pure_ppO2     = 999;
	else                               int_O_pure_ppO2     = (unsigned int)(100 *   OC_ppO2 + 0.5);
	
	// calculated pSCR ppO2
	if		( pSCR_ppO2     <  0.01  ) int_O_pSCR_ppO2     =   0;
	else if ( pSCR_ppO2     >= 9.995 ) int_O_pSCR_ppO2     = 999;
	else                               int_O_pSCR_ppO2     = (unsigned int)(100 * pSCR_ppO2 + 0.5);

	// breathed ppO2
	if		( ppO2          <  0.01  ) int_O_breathed_ppO2 =   0;
	else if ( ppO2          >= 9.995 ) int_O_breathed_ppO2 = 999;
	else                               int_O_breathed_ppO2 = (unsigned int)(100 *      ppO2 + 0.5);


	//---- Compute ppO2 Warnings ------------------------------------------------------------------

	// compute conditional min/max values
	int_ppO2_min = (char_O_main_status   & DECO_MODE_LOOP) ? (unsigned int)char_I_ppO2_min_loop : (unsigned int)char_I_ppO2_min;
	int_ppO2_max = (char_O_deco_warnings & DECO_FLAG     ) ? (unsigned int)char_I_ppO2_max_deco : (unsigned int)char_I_ppO2_max;

	// default value for the upper diluent ppO2 warning threshold is the normal upper warning threshold
	int_ppO2_max_dil = int_ppO2_max;

	// when in CCR mode, the upper diluent warning threshold gets adjust according to the current setpoint
	if( (char_O_main_status & DECO_MODE_MASK) == DECO_MODE_CCR )
	{
		overlay unsigned int max_dil;

		//  The upper diluent ppO2 threshold is ppO2_GAP_TO_SETPOINT below the setpoint...
		// (the condition protects from negative numbers which would cause a wrap-around)
		max_dil = (char_I_const_ppO2 > ppO2_GAP_TO_SETPOINT) ? (unsigned int)(char_I_const_ppO2 - ppO2_GAP_TO_SETPOINT) : 0;

		// ...but never above int_ppO2_max.
		if( max_dil < int_ppO2_max ) int_ppO2_max_dil = max_dil;

		// We do not need to guard int_ppO2_max_dil against becoming lower than char_I_ppO2_min because the check
		// against char_I_ppO2_min is done first and will then raise a low warning and inhibit further checks.
	}

	// check for safe range of pure oxygen
	if		( int_O_O2_ppO2       >=           int_ppO2_max     ) int_O_O2_ppO2       |= INT_FLAG_WARNING + INT_FLAG_HIGH;

	// check for safe range of breathed gas
	if		( int_O_breathed_ppO2 <=           int_ppO2_min     ) int_O_breathed_ppO2 |= INT_FLAG_WARNING + INT_FLAG_LOW;
	else if ( int_O_breathed_ppO2 >=           int_ppO2_max     ) int_O_breathed_ppO2 |= INT_FLAG_WARNING + INT_FLAG_HIGH;
	else if ( char_O_main_status  &            DECO_MODE_LOOP   ) ; // no attention generated in loop modes
	else if ( int_O_breathed_ppO2 >=   ppO2_ATTENTION_THRESHOLD ) int_O_breathed_ppO2 |= INT_FLAG_ATTENTION;

	// check for safe range of pure diluent
	if		( int_O_pure_ppO2 <= (unsigned int)char_I_ppO2_min  ) int_O_pure_ppO2     |= INT_FLAG_WARNING + INT_FLAG_LOW;
	else if ( int_O_pure_ppO2 >=               int_ppO2_max     ) int_O_pure_ppO2     |= INT_FLAG_WARNING + INT_FLAG_HIGH;
	else if ( int_O_pure_ppO2 >=               int_ppO2_max_dil ) int_O_pure_ppO2     |= INT_FLAG_ATTENTION;

	// check for safe range of calculated pSCR loop gas
	if		( int_O_pSCR_ppO2 <=               int_ppO2_min     ) int_O_pSCR_ppO2     |= INT_FLAG_WARNING + INT_FLAG_LOW;
	else if ( int_O_pSCR_ppO2 >=               int_ppO2_max     ) int_O_pSCR_ppO2     |= INT_FLAG_WARNING + INT_FLAG_HIGH;


	// done with the real tissues



	//---- Toggle between Calculation for NDL (bottom time),  -------------------------------------
	//---- Deco Stops, more Deco Stops and Results Gathering  -------------------------------------


	// all following operations target the simulated tissues, so clear flag in bit 7
	tissue_increment = 0;

	// branch to the code for the current phase the deco calculations are in
	switch( char_O_deco_status & DECO_STATUS_MASK )
	{
		overlay unsigned char i;

	case DECO_STATUS_INIT: //---- At surface: Start a new dive ---------------------

		// clear the internal stops table from remains lasting from the last dive
		clear_deco_table();

		// publish the cleared stops table to the display functions
		publish_deco_table();

		// clear the gas needs table
		for(i=0; i<NUM_GAS; ++i)
		{
			int_O_gas_volumes[i]    = 0;
			int_O_tank_pres_need[i] = 0 + INT_FLAG_ZERO;
		}

		// initialize the balancing between N2 and He for later no-fly time calculation
		for(i=0; i<NUM_COMP; ++i)
		{
			split_N2_He[i] = 90;	// assumes 90% of total tissue pressure will be needed for N2
		}
		
		// ** UNDER CONSTRUCTION - temporary code only **
		char_I_gas_change_time = 1;		// TODO: validate proper operation before enabling this options-table parameter		
		char_I_ascent_speed    = 10;	// TODO: validate proper operation before enabling this options-table parameter,
										//       caution: values < 10 may have an impact on the deco calculation run-times!

		// initialize values that are constant during the course of the dive
		float_ascent_speed            = 1.00  * char_I_ascent_speed;
		float_desaturation_multiplier = 0.01  * char_I_desaturation_multiplier;
		float_saturation_multiplier   = 0.01  * char_I_saturation_multiplier;
		float_deco_distance           = 0.01  * char_I_deco_distance;

		// initialize values that will be recalculated later on periodically
		char_O_nullzeit               = 0;	// reset NDL time for the normal plan
		char_O_alternate_nullzeit     = 0;	// reset NDL time for the alternative plan
		int_O_ascenttime              = 0;	// reset ascent time for the normal plan
		int_O_alternate_ascenttime    = 0;	// reset ascent time for the alternative plan
		char_O_deco_warnings          = 0;	// reset all deco warnings
		deco_tissue_vector            = 0;	// reset tissue deco vector
		IBCD_tissue_vector            = 0;	// reset tissue IBCD vector
		NDL_lead_tissue               = 0;	// reset first tissue to look at during NDL calculation

		// tag desaturation time as invalid (it will not be computed during a dive)
		int_O_desaturation_time       = 65535;

		// initialize CNS values
		int_O_normal_CNS_fraction = int_O_alternate_CNS_fraction = int_O_CNS_fraction;

		// Values that should be reset just once for the full real dive.
		// This is used to record the lowest stop for the whole dive,
		// including ACCROSS all simulated ascents.
		low_depth_norm      = low_depth_alt      = 0.0;
		locked_GF_step_norm = locked_GF_step_alt = 0.0;

		//
		// --> code execution continues in state DECO_STATUS_START
		//

	case DECO_STATUS_START: //---- Bottom Time & initial Ascent --------------------
	default:

		// clear the internal(!) stops table
		clear_deco_table();
		
		// initialize the simulated tissues with the current state of the real tissues
		for(i=0; i<NUM_COMP; i++)
		{
			sim_pres_tissue_N2[i] = pres_tissue_N2[i];
			sim_pres_tissue_He[i] = pres_tissue_He[i];
		}

		// Lookup the current gas and store it also as the first gas used.
		// This gas will be used for the bottom segment of the dive and for
		// the period of delayed ascent when calculating fTTS or bailout.
		gas_find_current();

		// setup the calculation ratio's for N2, He and O2 (sim_N2/He/O2_ratio)
		gas_set_ratios();

		// initialize depth in absolute pressure, it is needed by
		// - calc_alveolar_pressures(),
		// - calc_ascent_to_first_stop(), and
		// - calc_hauptroutine_calc_deco()
		sim_pres_respiration = pres_respiration;

		// calculate ppN2 and ppHe from sim_N2/He_ratio (<- tissue_increment has been set to 0)
		calc_alveolar_pressures();

		// calculate the effect of extended bottom time due to delayed ascent
		if( char_O_deco_status & DECO_ASCENT_DELAYED )
		{
			// program interval on simulated tissues (flag bit 7 = 0)
			tissue_increment = char_I_extra_time;

			// update the tissues
			calc_tissues();
		}

		// calculate if we are within no decompression limit (NDL)
		calc_NDL_time();

		// Calculate the initial ascent if in deco. calc_NDL_time() is very fast
		// in detecting being beyond NDL, so there is enough time left in this
		// phase to do the initial ascent calculation.
		if( NDL_time == 0 )
		{
			//--- in deco --------------------------------------------------------

			// calculate ascent to first stop
			calc_ascent_to_first_stop();

			// continue with calculating the stops
			char_O_deco_status &= ~DECO_STATUS_MASK;	// clear status bits and set status bits for
			char_O_deco_status |=  DECO_STATUS_STOPS;	// calculation of stops on next invocation
		}
		else
		{
			//--- within NDL -----------------------------------------------------

			// continue with gathering all results
			char_O_deco_status &= ~DECO_STATUS_MASK;
			char_O_deco_status |=  DECO_STATUS_RESULTS;
		}

		break;


	case DECO_STATUS_STOPS: //---- Calculate Stops ---------------------------------

		// calculate the stops
		calc_hauptroutine_calc_deco();

		// calc_hauptroutine_calc_deco iterates in this phase as long as it is
		// calculating the stops. Once done, it will set the status to doing the
		// results gathering.

		break;


	case DECO_STATUS_RESULTS: //--- Gathering of all Results -----------------------

		// if in normal plan, publish the stops table to the display functions
		if( !(char_O_deco_status & DECO_PLAN_ALTERNATE) ) publish_deco_table();

		// The current depth is needed by calc_CNS_planning() and gas_volumes().
		// As it may be needed in different code blocks below but we don't want
		// it to be in the code multiple times, it's done here on stockpile.
		bottom_depth = (unsigned char)((pres_respiration - pres_surface) * BAR_TO_METER);

		// Calculate the ascent time.
		// When within NDL, potential gas switches will be treated as done "on the fly".
		calc_ascenttime();

		// results to publish depend whether within NDL or in deco
		if( NDL_time )
		{
			//---- within NDL ----------------------------------------------

			// Calculate the initial ascent (not yet done when within NDL) - 
			// just to get potential gas switches into the stops table for use
			// by gas_volumes(). The stops table can be polluted by now because
			// the clean table has already been published to the display
			// functions before.
			calc_ascent_to_first_stop();

			// check which plan we are on
			if( char_O_deco_status & DECO_PLAN_ALTERNATE )
			{
				//---- alternate dive plan ---------------------------------

				// As we are in no stop, CNS at end of dive is more or less
				// the same CNS as we have right now. It's so simple that we
				// don't check if it requested to be computed or not...
				int_O_alternate_CNS_fraction = int_O_CNS_fraction;

				// output NDL time
				char_O_alternate_nullzeit  = NDL_time;

				// clear ascent time
				int_O_alternate_ascenttime = 0;
			}
			else
			{
				//---- normal dive plan ------------------------------------

				// As we are in no stop, CNS at end of dive is more or less
				// the same CNS as we have right now. It's so simple that we
				// don't check if it requested to be computed or not...
				int_O_normal_CNS_fraction = int_O_CNS_fraction;

				// output NDL time
				char_O_nullzeit = NDL_time;

				// clear ascent time
				int_O_ascenttime = 0;
			}
		} // NDL
		else
		{
			//---- in DECO -------------------------------------------------

			// check which plan we are on
			if( char_O_deco_status & DECO_PLAN_ALTERNATE )
			{
				//---- alternative plan ----------------------------------------------------

				// shall the CNS at the end of the dive be calculated?
				if( char_O_deco_status & DECO_CNS_CALCULATE )
				{
					// calculate the CNS for the predicted ascent, result in sim_CNS_fraction
					calc_CNS_planning();

					// add current CNS value
					sim_CNS_fraction += CNS_fraction;

					// convert to integer value
					convert_sim_CNS_for_display();

					// export result
					int_O_alternate_CNS_fraction = int_sim_CNS_fraction;
				}

				// clear NDL time
				char_O_alternate_nullzeit  = 0;

				// output ascent time
				int_O_alternate_ascenttime = ascent_time;

			} // alternative plan
			else
			{
				//---- normal plan ---------------------------------------------------------

				// shall the CNS at the end of the dive be calculated?
				if( char_O_deco_status & DECO_CNS_CALCULATE )
				{
					// calculate the CNS for the predicted ascent, result in sim_CNS_fraction
					calc_CNS_planning();

					// add current CNS value
					sim_CNS_fraction += CNS_fraction;

					// convert to integer value
					convert_sim_CNS_for_display();

					// export result
					int_O_normal_CNS_fraction = int_sim_CNS_fraction;
				}

				// clear NDL time
				char_O_nullzeit  = 0;

				// output ascent time
				int_O_ascenttime = ascent_time;

			} // normal plan
		} // DECO

		// if requested, calculate the required gas volumes and tank pressures at the end of the dive
		if( char_O_deco_status & DECO_VOLUME_CALCULATE ) gas_volumes();

		// signal that the computation cycle is finished
		char_O_deco_status &= ~DECO_STATUS_MASK;

		break;

	} // switch
}

//////////////////////////////////////////////////////////////////////////////
// calc_hauptroutine_data_input
//
// Set all C-code dive parameters from their ASM-code values.
// Detect gas change condition.
//
void calc_hauptroutine_data_input(void)
{
	overlay float IG_ratio;

	// get the current pressures
	pres_surface     = 0.001  * int_I_pres_surface;
	pres_respiration = 0.001  * int_I_pres_respiration;

	// N2 tissue pressure at surface equilibrium, used for tissue graphics scaling
	N2_equilibrium   = 0.7902 * (pres_surface - ppWater);

	// read the GF settings (they may have been switch between GF/aGF)
	GF_high  = 0.01 * char_I_GF_High_percentage;
	GF_low   = 0.01 * char_I_GF_Low_percentage;
	GF_delta = GF_high - GF_low;

	// get the currently breathed gas mixture
	O2_ratio = 0.01 * char_I_O2_ratio;
	He_ratio = 0.01 * char_I_He_ratio;

	// inert gas ratio (local helper variable)
	IG_ratio = 1.00 - O2_ratio;

	// N2 ratio
	N2_ratio = IG_ratio - He_ratio;

	// precomputed values for ppO2 drop in pSCR loop
	float_pSCR_factor = 0.01 * char_I_PSCR_drop * char_I_PSCR_lungratio;
	pSCR_drop         = IG_ratio * float_pSCR_factor;
}


//////////////////////////////////////////////////////////////////////////////
// Compute stops.
//
// Note: because this can be very long, break on 16 iterations, and set state
//       to DECO_STATUS_FINISHED when finished, or to DECO_STATUS_STOPS when
//       needing to continue.
// Note: because each iteration might be very long too (~ 66 ms in 1.84beta),
//       break the loop when elapsed time exceeds 512 milliseconds.
//
void calc_hauptroutine_calc_deco(void)
{
	overlay unsigned char loop;


	for(loop = 0; loop < 16; ++loop)
	{
		// limit loops to 512ms, using timer 5
		if( tmr5() & (512*32) ) break;

		// calc_nextdecodepth()
		//
		// INPUT  sim_pres_respiration : current depth in absolute pressure
		// OUTPUT sim_depth_limit      : depth of next stop in meters
		// RETURN true if a stop is needed
		//
		// The function manages gas changes by itself, including priming
		// the deco stop with the configured gas change time.
		//
		if( calc_nextdecodepth() )
		{
			if( sim_depth_limit == 0 ) goto Surface;	// this check should not bee needed as in
														// this case the RETURN value will be false

			//---- stop required at sim_depth_limit -------------------------------------

			// convert stop depth in meters to absolute pressure
			sim_pres_respiration = sim_depth_limit * METER_TO_BAR + pres_surface;

			// add one minute to the current stop, or add a new stop,
			// or abort deco calculation if the deco table is full.
			if( !update_deco_table(1) ) goto Surface;
		}
		else
		{
			//---- no stop required --------------------------------------

			// ascend by float_ascent_speed for 1 minute
			sim_pres_respiration -= float_ascent_speed * METER_TO_BAR;

			// finish deco calculation if surface is reached
			if( sim_pres_respiration <= pres_surface )
			{
Surface:
				// continue with gathering all results in the next calculation phase
				char_O_deco_status &= ~DECO_STATUS_MASK;
				char_O_deco_status |=  DECO_STATUS_RESULTS;

				return;
			}
		}


		//---- as one minute as passed now, update the tissues ----------------------

		// program 1 minute interval on simulated tissues (Flagbit 7 = 0)
		tissue_increment = 1;

		// compute current ppN2 and ppHe
		calc_alveolar_pressures();

		// update the tissues
		calc_tissues();
	}
}


//////////////////////////////////////////////////////////////////////////////
// Calculate ascent to first deco stop.
//
//
// Modified: sim_pres_respiration : current depth in ascent and deco simulation, in bar absolute pressure
//
void calc_ascent_to_first_stop(void)
{
	overlay unsigned char fast      = 1;	// 1 = 1 minute steps,  0 = 2 seconds steps
	overlay unsigned char gaschange = 0;	// 1 = do a gas change, 0 = no better gas available


	//---- Loop until first deco stop or surface is reached ----------
	for(;;)
	{
		// depth in absolute pressure we came from
		overlay float old_deco = sim_pres_respiration;

		// try ascending 1 full minute (fast) or 2 seconds (!fast)
		if	( fast ) sim_pres_respiration -=  float_ascent_speed       * METER_TO_BAR;	// 1 min at float_ascent_speed ( 5 .. 10  m/min)
		else         sim_pres_respiration -= (float_ascent_speed/30.0) * METER_TO_BAR;	// 2 sec at float_ascent_speed (17 .. 33 cm/min)

		// but don't go over surface
		if( sim_pres_respiration < pres_surface ) sim_pres_respiration = pres_surface;

		// compute current ceiling of the simulated tissues
		if   ( char_I_deco_model != 0 ) calc_limit(GF_low);
		else                            calc_limit(1.0);

		// did we overshoot the first deco stop?
		if( sim_pres_respiration < (sim_ceiling + pres_surface) )
		{
			// YES - back to last depth below first stop
			sim_pres_respiration = old_deco;

			// switch to 2 seconds ascent if not yet in, else done
			if( fast )
			{
				fast = 0;				// retry with 2 seconds ascent steps
				continue;
			}
			else
			{
				break;					// done...
			}
		}

		// If code execution passes along here, we did not overshoot the first stop.

		// did we reach the surface? if yes, done!
		if( sim_pres_respiration == pres_surface ) break;

		// depth in meters where we are now (no round-up)
		sim_depth_limit = (unsigned char)((sim_pres_respiration - pres_surface) * BAR_TO_METER);

		// Check if there is a better gas to switch to, but only in alternative plan mode
		// or if override is set. If yes, introduce a stop for the gas change.
		if(    ((char_O_deco_status & DECO_PLAN_ALTERNATE) || (char_O_main_status & DECO_GASCHANGE_OVRD))
			&& gas_find_better() )
		{
			// depth in meters we came from
			overlay unsigned char old_depth_limit = (unsigned char)((old_deco - pres_surface) * BAR_TO_METER);			

			// adjust sim_depth_limit to the gas change depth, but not deeper than the depth we came from
			sim_depth_limit = (sim_gas_last_depth < old_depth_limit) ? sim_gas_last_depth : old_depth_limit;

			// create a stop for the gas change
			update_deco_table(char_I_gas_change_time);

			// set the new calculation values for N2, He and O2
			gas_set_ratios();

			// signal to create a stop for the gas change and update the tissues
			gaschange = char_I_gas_change_time;

			// Adjust the depth for the tissue update to the stop depth. In case of fast mode, this
			// imposes that the ascent from the 'old_deco' depth to this stop took 1 minute although
			// we might have only ascended one or two meters...
			sim_pres_respiration = sim_depth_limit * METER_TO_BAR + pres_surface;
		}

		// Did one minute pass by and/or do we have a gas change?
		// Remark: The 2 seconds ascent iterations towards the first deco stop in !fast speed may take
		// up to 28 seconds in total - for this rough half of a minute no tissue updates will be computed.
		// Well, it could be done by setting tissue_increment = 0 in !fast condition and making calls to
		// calc_alveolar_pressures() and calc_tissues() - see code commented out below.
		if( fast || gaschange )
		{
			// program interval on simulated tissues (flag bit 7 = 0)
			tissue_increment = fast + gaschange;

			// clear gas change signal
			gaschange = 0;
	//	}
	//	else
	//	{
	//		// program 2 seconds interval on simulated tissues (flag bit 7 = 0)
	//		tissue_increment = 0;
	//	}
	//	{
			// compute ppN2/ppHe for current depth from sim_pres_respiration
			calc_alveolar_pressures();
			
			// update the tissues
			calc_tissues();
		}
	}
}


//////////////////////////////////////////////////////////////////////////////
// calc_tissues
//
// INPUT:    ppN2                          : partial pressure of inspired N2
//           ppHe                          : partial pressure of inspired He
//           tissue_increment              : integration time and tissue selector (real or simulated)
//
// MODIFIED: pres_tissue_N2[]              : tissue N2 pressures (in real tissues context)
//           pres_tissue_He[]              : tissue He pressures (in real tissues context)
//           sim_pres_tissue_N2[]          : tissue N2 pressures (in simulated tissues context)
//           sim_pres_tissue_He[]          : tissue He pressures (in simulated tissues context)
//
// OUTPUT:   char_O_tissue_N2_saturation[] : tissue N2 pressures scaled for display purpose (in real tissues context)
//           char_O_tissue_He_saturation[] : tissue He pressures scaled for display purpose (in real tissues context)
//
static void calc_tissues()
{
	overlay float			temp_tissue_N2;
	overlay float			temp_tissue_He;
	overlay unsigned char	period;
	overlay unsigned char	i;


	assert( 0.00 <= ppN2 && ppN2 < 11.2 );  // 80% N2 at 130m
	assert( 0.00 <= ppHe && ppHe < 12.6 );  // 90% He at 130m


	for (ci=0;ci<NUM_COMP;ci++)				// iterate through all compartments
	{
		i = tissue_increment & TIME_MASK;	// extract number of minutes to do    (if i > 0)
											// or if one 2 second period is to do (if i = 0)

		if( i == 0 )						// check if we shall do one 2-seconds period
		{
			read_Buhlmann_times(0);			// YES, program coefficients for a 2 seconds period
			period = 1;						//      set period length (in cycles)
			i      = 1;						//      and one cycle to do
		}
		else if( i > 9 )					// check if we can start with 10 minutes periods
		{
			read_Buhlmann_times(2);			// YES, program coefficients for 10 minutes periods
			period = 10;					//      set period length (in cycles) to ten
		}
		else								// we shall do 1 to 9 minutes
		{
			read_Buhlmann_times(1);			//      program coefficients for 1 minute periods
			period = 1;						//      set period length (in cycles) to one
		}

		do
		{
			//---- N2 -------------------------------------------------------------------------------

			temp_tissue = (tissue_increment & TISSUE_FLAG) ? pres_tissue_N2[ci] : sim_pres_tissue_N2[ci];

			temp_tissue = (ppN2 - temp_tissue) * var_N2_e;

			temp_tissue_safety();

			if( tissue_increment & TISSUE_FLAG )
			{
				temp_tissue_N2      = temp_tissue;
				pres_tissue_N2[ci] += temp_tissue;
			}
			else
			{
				sim_pres_tissue_N2[ci] += temp_tissue;
			}


			//---- He -------------------------------------------------------------------------------

			temp_tissue = (tissue_increment & TISSUE_FLAG) ? pres_tissue_He[ci] : sim_pres_tissue_He[ci];

			temp_tissue = (ppHe - temp_tissue) * var_He_e;

			temp_tissue_safety();

			if( tissue_increment & TISSUE_FLAG )
			{
				temp_tissue_He      = temp_tissue;
				pres_tissue_He[ci] += temp_tissue;
			}
			else
			{
				sim_pres_tissue_He[ci] += temp_tissue;
			}

			// decrement loop counter
			i -= period;

			// check if we need to switch from 10 minute periods to 1 minute periods
			if( (i > 0) && (period = 10) && (i < 10) )
			{
				read_Buhlmann_times(1);		// program coefficients for 1 minute periods
				period = 1;					// set period length (in cycles) to one
			}
		}
		while( i );


		// have the computations been done for the "real" tissues?
		if( tissue_increment & TISSUE_FLAG )
		{
			// net tissue balance
			temp_tissue = temp_tissue_N2 + temp_tissue_He;

			// check tissue on-/off-gassing and IBCD with applying a threshold of +/-HYST
			//
			if      ( temp_tissue < -HYST )				// check if the tissue is off-gassing
			{
				deco_tissue_vector |=  (1 << ci);		// tag tissue as being in decompression
				IBCD_tissue_vector &= ~(1 << ci);		// tag tissue as not experiencing mentionable IBCD
			}
			else if ( temp_tissue > +HYST )				// check if the tissue in on-gassing
			{
				deco_tissue_vector &= ~(1 << ci);		// tag tissue as not being in decompression

				if(		((temp_tissue_N2 > 0.0) && (temp_tissue_He < 0.0))		// check for counter diffusion
					||	((temp_tissue_N2 < 0.0) && (temp_tissue_He > 0.0)) )
				{
					IBCD_tissue_vector |= (1 << ci);	// tag tissue as experiencing mentionable IBCD
				}
			}


			// keep the saturating / desaturating flags from last invocation
			char_O_tissue_N2_saturation[ci] &= 128;
			char_O_tissue_He_saturation[ci]	&= 128;

			// flip the flags applying a hysteresis of HYST (actual value: see #define of HYST)
				 if( temp_tissue_N2 > +HYST ) char_O_tissue_N2_saturation[ci] = 128; // set flag for tissue pressure is increasing
			else if( temp_tissue_N2 < -HYST ) char_O_tissue_N2_saturation[ci] =   0; // clear flag (-> tissue pressure is decreasing)

				 if( temp_tissue_He > +HYST ) char_O_tissue_He_saturation[ci] = 128; // set flag for tissue pressure is increasing
			else if( temp_tissue_He < -HYST ) char_O_tissue_He_saturation[ci] =   0; // clear flag (-> tissue pressure is decreasing)


			// For N2 tissue display purpose:
			// Scale tissue press so that saturation in 70m on AIR gives a value of approx. 80.
			// The surface steady-state tissue loading of [0.7902 * (pres_respiration - ppWater)] bar
			// gives then a 10. If N2 is completely washed out of the tissue, result will be 0.
			// This scaling is adapted to the capabilities of the tissue graphics in the custom views.
			temp_tissue = (pres_tissue_N2[ci] / N2_equilibrium) * 10;

			// limit to 127 to leave space for sat/desat flag
			if (temp_tissue > 127) temp_tissue = 127;

			// export as integer
			char_O_tissue_N2_saturation[ci] += (unsigned char)temp_tissue;


			// For H2 tissue display purpose:
			// Scale tissue press so that saturation in 120m on TMX 10/70 gives a value of approx. 70.
			// With no He in a tissue, result will be 0.
			// This scaling is adapted to the capabilities of the tissue graphics in the custom views.
			temp_tissue = pres_tissue_He[ci] * 7.7;

			// limit to 127 to leave space for sat/desat flag
			if (temp_tissue > 127) temp_tissue = 127; 

			// export as integer
			char_O_tissue_He_saturation[ci] += (unsigned char)temp_tissue;
		} //if

	} // for


	// set   deco flag if we are in deco and at least one of the real tissues is off-gassing
	// clear deco flag if all of the real tissues are on-gassing
	if      ( (char_O_nullzeit == 0) &&  deco_tissue_vector ) char_O_deco_warnings |=  DECO_FLAG;
	else if (                           !deco_tissue_vector ) char_O_deco_warnings &= ~DECO_FLAG; 
}

//////////////////////////////////////////////////////////////////////////////
// calc_limit
//
// Input:
//        tissue_increment       : selector for context: real or simulated tissues
//        sim_pres_tissue_N2/_He : tissue pressures (used in simulated tissues context)
//        pres_tissue_N2/_He     : tissue pressures (used in real      tissues context)
//
// Output:
//        sim_ceiling            : ceiling in bar  relative pressure (only in simulated tissues context)
//        ceiling                : ceiling in bar  relative pressure (only in real      tissues context)
//        int_O_ceiling          : ceiling in mbar relative pressure (only in real      tissues context)
//        int_O_gradient_factor  : gradient factor in %              (only in real      tissues context)
//
// Modified:
//        char_O_deco_warnings   : for IBCD, microbubbles and outside warning (only in real tissues context)
//
static void calc_limit(PARAMETER float GF_parameter)
{
	overlay float lead_tissue_limit = 0.0;
	overlay float lead_supersat     = 0.0;

	overlay unsigned char lead_tissue_no = 0;


	// check context
	if( tissue_increment & TISSUE_FLAG )
	{
		// clear IBCD, microbubbles and outside warning flags (locked warnings will be preserved)
		char_O_deco_warnings &= ~(DECO_WARNING_IBCD + DECO_WARNING_MBUBBLES + DECO_WARNING_OUTSIDE);
	}


	// loop over all tissues
	for(ci=0; ci<NUM_COMP; ci++)
	{
		overlay float calc_pres_tissue_N2;
		overlay float calc_pres_tissue_He;
		overlay float pres_tissue;
		overlay float pres_min;

		// get the tissue pressures
		if( tissue_increment & TISSUE_FLAG )
		{
			// context is real tissues
			calc_pres_tissue_N2 = pres_tissue_N2[ci];
			calc_pres_tissue_He = pres_tissue_He[ci];
		}
		else
		{
			// context is simulated tissues
			calc_pres_tissue_N2 = sim_pres_tissue_N2[ci];
			calc_pres_tissue_He = sim_pres_tissue_He[ci];
		}

		// overall tissue pressure
		pres_tissue = calc_pres_tissue_N2 + calc_pres_tissue_He;

		// get the coefficients for tissue ci
		read_Buhlmann_coefficients();

		// adapt the coefficients according to the N2/He ratio in the tissue
		var_N2_a = (var_N2_a * calc_pres_tissue_N2 + var_He_a * calc_pres_tissue_He) / pres_tissue;
		var_N2_b = (var_N2_b * calc_pres_tissue_N2 + var_He_b * calc_pres_tissue_He) / pres_tissue;

		// calculate minimum ambient pressure that the tissue can withstand according to straight Buhlmann
		pres_min = (pres_tissue - var_N2_a) * var_N2_b;

		// next calculations are only relevant when invoked on the real tissues
		if( tissue_increment & TISSUE_FLAG )
		{
			overlay float supersat;
			overlay float threshold;

			// calculate current supersaturation value (1.0 = 100%) of this tissue
			supersat = (pres_tissue - pres_respiration) / (pres_tissue - pres_min);

			// check if tissue is in supersaturation
			if( supersat > 0.0 )
			{
				// memorize highest supersaturation found
				if( supersat > lead_supersat ) lead_supersat = supersat;

				// set a threshold value for the microbubbles and outside warnings
				// ToDo: finalize the definition of the threshold
				threshold = 0.02 * ci + 0.9;

				// check if this tissue is likely to develop microbubbles
				// and/or if this tissue is outside of the Buhlmann model
				if( ci <= 5 )
				{
					if( supersat >= threshold )
					{
						char_O_deco_warnings |= (DECO_WARNING_MBUBBLES + DECO_WARNING_MBUBBLES_lock);

						if( supersat >= 1.0 )
						{
							char_O_deco_warnings |= (DECO_WARNING_OUTSIDE + DECO_WARNING_OUTSIDE_lock);
						}
					}
				}
				else // ci > 5
				{
					if( supersat >= 1.0 )
					{
						char_O_deco_warnings |= (DECO_WARNING_MBUBBLES + DECO_WARNING_MBUBBLES_lock);

						if( supersat >= threshold )
						{
							char_O_deco_warnings |= (DECO_WARNING_OUTSIDE + DECO_WARNING_OUTSIDE_lock);
						}
					}
				}
			}
		}

		// Apply the Eric Baker's varying gradient factor correction if the GF-Model is selected.
		// Note: the correction factor depends both on GF and b, so that can change who is the
		//       leading gas...
		if( char_I_deco_model != 0 ) pres_min =   ( pres_tissue        - (var_N2_a     * GF_parameter) )
		                                        / ( 1.0 - GF_parameter + (GF_parameter / var_N2_b    ) );

		// check if this tissue requires a higher ambient pressure than was found to be needed up to now
		if( pres_min > lead_tissue_limit )
		{
			lead_tissue_limit = pres_min;
			lead_tissue_no    = ci;
		}
	} // for


	// compile outputs
	if( tissue_increment & TISSUE_FLAG )
	{
		//--- real tissues -----------------------------------------------------

		// check if leading tissue is in IBCD condition
		if(    (IBCD_tissue_vector & (1 << lead_tissue_no))
		    && ((pres_tissue_N2[lead_tissue_no] + pres_tissue_He[lead_tissue_no]) > pres_respiration) )
		{
			// leading tissue is in IBCD condition and in super-saturation, so issue a warning.
			char_O_deco_warnings |= (DECO_WARNING_IBCD + DECO_WARNING_IBCD_lock);
		}


		// compute ceiling in bar relative pressure
		ceiling = lead_tissue_limit - pres_surface;

		// convert ceiling to int_O_ceiling in mbar
		if      ( ceiling <=  0 ) int_O_ceiling = 0;
		else if ( ceiling >  16 ) int_O_ceiling = 16000;
	// Compatibility version
	//	else                      int_O_ceiling = (short)(ceiling * 1000);

	// New version: Rounds up to next 10 cm so that the ceiling disappears on the display only when the
	// ceiling limit is really zero. This will coincident then with TTS switching back to NDL time.
		else                      int_O_ceiling = (short)(ceiling * 1000 + 9);


		// convert highest supersaturation found to int_O_gradient_factor in % (1.0 = 100%)
		// limit to 255 because of constraints in ghostwriter code
		if     ( lead_supersat <= 0.0   ) int_O_gradient_factor = 0;
		else if( lead_supersat >  2.545 ) int_O_gradient_factor = 255 + INT_FLAG_WARNING;
		else
		{
			int_O_gradient_factor = (unsigned int)(100 * lead_supersat + 0.5);

			if      ( int_O_gradient_factor >= GF_WARNING_THRESHOLD      )
				int_O_gradient_factor |= INT_FLAG_WARNING;

			else if ( int_O_gradient_factor >= char_I_GF_High_percentage )
				int_O_gradient_factor |= INT_FLAG_ATTENTION;
		}
	}
	else
	{
		//--- simulated tissues ------------------------------------------------
		
		// compute ceiling for the simulated tissues in bar relative pressure
		sim_ceiling = lead_tissue_limit - pres_surface;
	}
}

//////////////////////////////////////////////////////////////////////////////
// calc_NDL_time
//
// calculates the remaining bottom time
//
// NOTE: Erik Baker's closed formula works for Nitroxes. Trimix adds a second
//       exponential term to the M-value equation, making it impossible to
//       invert... So we have to make a fast-simu until we find a better way.
//
// Input:  ppN2
//         ppHe
//
// Output: NDL_time
//
static void calc_NDL_time(void)
{
	overlay unsigned char new_NDL_lead_tissue = 0;
	overlay unsigned char i;


	// initialize NDL_time to 240 minutes
	NDL_time = 240;

	for(i=0; i<NUM_COMP; i++)
	{
		overlay float calc_pres_tissue_N2;
		overlay float calc_pres_tissue_He;
		overlay float pres_tissue;

		overlay unsigned char NDL_tissue;
		overlay unsigned char period = 10;		// start with 10 minute periods


		// check lead tissue from last NDL computation first
		ci = i + NDL_lead_tissue;

		// wrap around after the 16th tissue
		if( ci >= NUM_COMP ) ci -= NUM_COMP;

		// read Buhlmann a and b coefficients for tissue ci
		read_Buhlmann_coefficients();

		// read the loading factors for 10 minute periods
		read_Buhlmann_times(2);
		
		// get the tissue pressures for N2 and He
		calc_pres_tissue_N2 = sim_pres_tissue_N2[ci];
		calc_pres_tissue_He = sim_pres_tissue_He[ci];

		// calculate the total pressure tissue
		pres_tissue = calc_pres_tissue_N2 + calc_pres_tissue_He;


		// simulate an increasing bottom time and check when we hit the NDL ------------------------
		for( NDL_tissue = 0; NDL_tissue < NDL_time; )	// not needed to simulate for longer than the already found NDL
		{
			overlay float var_a;
			overlay float var_b;
			overlay float pres_limit;
			overlay float delta_pres_tissue_N2;
			overlay float delta_pres_tissue_He;


			// adopt a and b coefficients to current N2/He ratio inside the tissue
			var_a = (var_N2_a * calc_pres_tissue_N2 + var_He_a * calc_pres_tissue_He) / pres_tissue;
			var_b = (var_N2_b * calc_pres_tissue_N2 + var_He_b * calc_pres_tissue_He) / pres_tissue;

			// compute pressure limit for tissues under surface pressure conditions
			pres_limit = (var_a + pres_surface / var_b);

			// adopt pressure limit when using the GF extension
			if (char_I_deco_model != 0 ) pres_limit = GF_high * (pres_limit - pres_surface) + pres_surface;

			//---- Check if this tissue is already beyond the NDL
			if( pres_tissue > pres_limit)
			{
				// NO - finish the outer loop,
				i = NUM_COMP;

				// and finish the inner loop
				break;
			}

			// compute delta to tissue pressures in 10 or 1 minutes of time ahead
			delta_pres_tissue_N2 = (ppN2 - calc_pres_tissue_N2) * var_N2_e;
			delta_pres_tissue_He = (ppHe - calc_pres_tissue_He) * var_He_e;

			// apply safety factors to the pressure deltas
			// NDL can be computed while ascending, so we have to check if we are saturating or desaturating
			if( delta_pres_tissue_N2 > 0.0 ) delta_pres_tissue_N2 *= float_saturation_multiplier;
			else                             delta_pres_tissue_N2 *= float_desaturation_multiplier;

			if( delta_pres_tissue_He > 0.0 ) delta_pres_tissue_He *= float_saturation_multiplier;
			else                             delta_pres_tissue_He *= float_saturation_multiplier;

			// Simulate off-gassing while going to surface
			// TODO !
			// delta_pres_tissue_N2 -= exp( ... ascent time ... ppN2...)
			// delta_pres_tissue_He -= exp( ... ascent time ... ppHe...)

			// within NDL now, but still within in 10 or 1 minutes from now?
			if( pres_tissue + delta_pres_tissue_N2 + delta_pres_tissue_He <= pres_limit )
			{
				// YES - apply the pressure deltas to tissues
				calc_pres_tissue_N2 += delta_pres_tissue_N2;
				calc_pres_tissue_He += delta_pres_tissue_He;
				
				// update the overall tissue pressure
				pres_tissue = calc_pres_tissue_N2 + calc_pres_tissue_He;

				// increment the NDL
				NDL_tissue += period;

				// do next loop
				continue;
			}

			// NO - if delta pressures were for 10 minutes of time ahead, try with 1 minute ahead
			if( period == 10 )
			{
				// reduce period to 1 minute
				period = 1;

				// read the loading factors for 1 minute periods
				read_Buhlmann_times(1);

				// do next loop
				continue;
			}

			// NO - not even within NDL in just one more minute, so make a linear approx for the last minute
			// (make a meaningful rounding of NDL, but ONLY if positive: negative casted to unsigned is bad)
			if( pres_limit > pres_tissue )
				NDL_tissue += (unsigned char)(0.5 +    (pres_limit           - pres_tissue         )
			                                  / (delta_pres_tissue_N2 + delta_pres_tissue_He) );

			// finish the inner loop
			break;
		}

		// is the current NDL short than the shortest so far?
		if ( NDL_tissue < NDL_time )
		{
			// keep the current's tissue NDL as the new shortest NDL
			NDL_time = NDL_tissue;

			// store the causing tissue
			new_NDL_lead_tissue = ci;
		}
		
		// if NDL is > 0 the outer loop will continues with the next tissue
		// if NDL found to be overrun, outer loop will be terminated through i = NUM_COMP statement
	}

	// store the NDL dominating tissue for to start with in the next NDL calculation
	NDL_lead_tissue = new_NDL_lead_tissue;
}

//////////////////////////////////////////////////////////////////////////////
// calc_ascenttime
//
// Sum up ascent from bottom to surface at float_ascent_speed,
// but 1 minute per meter for the final ascent, and all stops.
//
// Input:  char_I_depth_last_deco
//         pres_respiration
//         pres_surface
//         float_ascent_speed
//         internal_deco_depth[]
//
// Output: ascent_time
//
static void calc_ascenttime(void)
{
	overlay unsigned char x;


	// preset final ascent
	overlay float final  = (float)char_I_depth_last_deco;

	// calculate depth
	overlay float ascent = (pres_respiration - pres_surface) * BAR_TO_METER;

	// check if we are already in final ascent
	if (ascent <= final)
	{
		// yes - all ascent is final ascent
		final  = ascent;
		ascent = 0.0;
	}
	else
	{
		// no - subtract final ascent part from overall ascent
		ascent -= final;

		// compute time for ascent part without final ascent
		ascent /= float_ascent_speed;
	}

	// add 1 minute for each meter of final ascent
	ascent += final;

	// convert to integer
	ascent_time = (unsigned short)(ascent + 0.5);

	// add all stop times
	for(x=0; x<NUM_STOPS && internal_deco_depth[x]; x++)
		ascent_time += (unsigned short)internal_deco_time[x];

	// limit result to display max.
	if( ascent_time > 999) ascent_time = 999;

	// tag result as invalid if there is an overflow in the stops table
	if( char_O_deco_warnings & DECO_WARNING_STOPTABLE_OVERFLOW ) ascent_time |= INT_FLAG_INVALID;
}


//////////////////////////////////////////////////////////////////////////////
// clear_deco_table
//
//
static void clear_deco_table(void)
{
	overlay unsigned char x;

	for(x=0; x<NUM_STOPS; ++x)
	{
		internal_deco_time [x] = 0;
		internal_deco_depth[x] = 0;
	}

	// clear stop table overflow warning
	char_O_deco_warnings &= ~DECO_WARNING_STOPTABLE_OVERFLOW;
}

//////////////////////////////////////////////////////////////////////////////
// update_deco_table
//
// Add time to a stop at sim_depth_limit
//
// It is possible to create stops with a duration of 0 minutes, e.g. to
// note a gas change "on the fly" while ascending. Therefore the criteria
// to have reached the end of the list needs always to be depth == 0.
//
// Input:   sim_depth_limit  : stop's depth, in meters.
//          sim_gas_last_used : gas used at stop, as index 1..5 or 0 for gas 6
//          PARAMETER time_increment    : number of minutes to add to the stop
//
// Updated: internal_deco_depth[] : depth    (in meters)  of each stop
//          internal_deco_time [] : time     (in minutes) of each stop
//          internal_deco_gas  [] : gas used (index 1-5)  at each stop
//
static unsigned char update_deco_table(PARAMETER unsigned char time_increment)
{
	overlay unsigned char x;

	assert( sim_depth_limit > 0 );		// No stop at surface...

	// loop through internal deco table
	for(x=0; x<NUM_STOPS; ++x)
	{
		// Make sure deco-stops are recorded in order:
		assert( !internal_deco_depth[x] || sim_depth_limit <= internal_deco_depth[x] );

		// Is there already a stop entry for our current depth?
		if( internal_deco_depth[x] == sim_depth_limit )
		{
			// Yes - increment stop time if possible
			// Stop time entries are limited to 99 minutes because of display constraints.
			// Else a limit of 254 would account because of constrains in calc_CNS_planning().
			if( internal_deco_time[x] < (100 - time_increment) )
			{
				internal_deco_time[x] += time_increment;	// increment stop time
				return 1;									// return with status 'success'
			}
		}

		// If program flow passes here, there is either no stop entry for the current depth yet, or
		// the existing entry is saturated with 99 minutes. So we are looking for the next unused
		// table entry.
		if( internal_deco_depth[x] == 0 )
		{
			internal_deco_time[x]  = time_increment;		// initialize entry with first stop's time,
			internal_deco_depth[x] = sim_depth_limit;		// ... depth, and
			internal_deco_gas[x]   = sim_gas_last_used;		// ... gas
			return 1;										// return with status 'success'
		}
	}

	// If program flow passes here, all deco table entries are used up.

	// set overflow warning
	char_O_deco_warnings |= DECO_WARNING_STOPTABLE_OVERFLOW;

	// return with status 'failed'.
	return 0;
}


//////////////////////////////////////////////////////////////////////////////
// calc_desaturation_time
//
// Inputs:  int_I_pres_surface, ppWater, char_I_desaturation_multiplier
// Outputs: int_O_desaturation_time, int_O_nofly_time
//
// Helper function
//
void calc_desaturation_time_helper(void)
{
	if( pres_actual > pres_target )		// check if actual pressure is higher then target pressure
	{									// YES - compute remaining time
		overlay	float pres_ratio;

		pres_ratio = pres_actual / pres_target;

		// Compute desaturation time with result rounded up to multiples of 10 minutes.
		// Main purpose is to avoid confusion, because the times do not clock down in
		// one minute steps any more but get constantly re-computed according to current
		// ambient pressure and may therefor make steps of several minutes forwards and
		// backwards as ambient pressure rises and falls.
		int_time = (unsigned int)( (var_ht * log(pres_ratio) / desat_factor) + 0.9 );
	}
	else
	{									// NO  - desaturation state reached, no remaining time
		int_time = 0;
	}
}

/////////////////////////////////////////////////////////////////////////////
// Main function
//
void calc_desaturation_time(void)
{
	assert( 800 < int_I_pres_surface             && int_I_pres_surface             < 1100 );
	assert( 0   < char_I_desaturation_multiplier && char_I_desaturation_multiplier <= 100 );

	// fraction of inert gases in respired air
	N2_ratio       = 0.7902;
	He_ratio       = 0.0;

	// surface pressure in bar
	pres_surface   = 0.001    * int_I_pres_surface;

	// partial pressure of N2 in respired air
	N2_equilibrium = N2_ratio * (pres_surface - ppWater);

	// pre-computed term for later use: 10 [Min] * 0.01 [%] * 0.6931 [=log(2)] * ...
	desat_factor   = 0.06931  * char_I_desaturation_multiplier * SURFACE_DESAT_FACTOR;

	// initialize vars
	int_O_desaturation_time = 0;
	int_O_nofly_time        = 0;


	for(ci=NUM_COMP; ci>0;)
	{
		overlay float        pres_tissue_max;
		overlay float        P_ambient_altitude;
		overlay signed char  search_direction;
		overlay unsigned int nofly_N2   =  0;
		overlay unsigned int nofly_He   =  0;
		overlay unsigned int nofly_last = ~0;


		ci -= 1;

		read_Buhlmann_ht();
		read_Buhlmann_coefficients();

		// get selected target altitude
		switch( char_I_altitude_wait )
		{
			case 1:  P_ambient_altitude = P_ambient_1000m;	break;
			case 2:  P_ambient_altitude = P_ambient_2000m;	break;
			case 3:  P_ambient_altitude = P_ambient_3000m;	break;
			default: P_ambient_altitude = P_ambient_fly;	break;
		}

		// Target pressure for the tissue is the Buhlmann limit. We use the Buhlmann
		// coefficients for N2 also for He because it is easier to calculate and the
		// N2 coefficients are more conservative than those for He, so we are on the
		// safe side, too.
		pres_tissue_max = (P_ambient_altitude/var_N2_b + var_N2_a);

		// Adjust target pressure in case the GF model is in use by GF-high
		if( char_I_deco_model != 0 )
			pres_tissue_max = P_ambient_altitude +
			                  0.01 * char_I_GF_High_percentage * (pres_tissue_max - P_ambient_altitude);


		//
		// Desaturation time
		//

		// N2: actual amount of tissue pressure above equilibrium.
		pres_actual = pres_tissue_N2[ci] - N2_equilibrium;
		
		// N2: half-time of the current tissue
		var_ht      = var_N2_ht;

		// Calculate desaturation time for N2 in tissue.
		// Desaturated state is defined as residual tissue pressure <= 1.05 x ppN2 respired

		pres_target = 0.05 * N2_equilibrium;

		calc_desaturation_time_helper();

		if( int_time > int_O_desaturation_time) int_O_desaturation_time = int_time;


		// He: actual amount of tissue pressure above equilibrium: equilibrium for He is 0 bar
		pres_actual = pres_tissue_He[ci];

		// He: half-time of the current tissue
		var_ht      = var_He_ht;

		// Calculate desaturation time for He in the tissue.
		// Desaturated state is defined as residual tissue pressure <= 0.05 x ppN2 respired

		pres_target = 0.05 * N2_equilibrium;

		calc_desaturation_time_helper();

		if( int_time > int_O_desaturation_time) int_O_desaturation_time = int_time;


		//
		// no-fly time
		//

		// initialize search direction
		search_direction = 0;

		for(;;)
		{
			// N2: actual amount of tissue pressure above equilibrium.
			pres_actual = pres_tissue_N2[ci] - N2_equilibrium;

			// N2: half-time of the current tissue
			var_ht      = var_N2_ht;

			// Calculate no-fly time for N2 in the tissue.
			// Flying is permitted when the N2 pressure fits into the assigned fraction above equilibrium.

			pres_target = (split_N2_He[ci] * 0.01) * (pres_tissue_max - N2_equilibrium);

			if( pres_target < 0.0 )						// check if desaturation to fly target is possible
			{
				int_O_nofly_time = 288;					// NO  - set no-fly time to 288 * 10 min = 48 h
				break;									// done for this compartment
			}
			else 
			{
				calc_desaturation_time_helper();
				nofly_N2 = int_time;
			}

			// He: actual amount of tissue pressure above equilibrium - equilibrium for He is 0 bar.
			pres_actual = pres_tissue_He[ci];

			// He: half-time of the current tissue
			var_ht      = var_He_ht;

			// Calculate no-fly time for He in the tissue.
			// Flying is permitted when the He pressure fits into the assigned fraction.

			pres_target = (0.01 * (100 - split_N2_He[ci])) * (pres_tissue_max - N2_equilibrium);

			calc_desaturation_time_helper();
			nofly_He = int_time;


			// Because the sum of N2 and He tissue pressures needs to fit into the Buhlmann limit for
			// no-fly time calculation, each gas gets assigned a fraction of the available total pressure
			// limit. The optimum split between the two gases can not be computed by a single formular,
			// because this would require the inversion of a function with two exponential terms, which is
			// not possible. We do not want to do a computational complex simulation here like it is done
			// in the deco calculation code (although we tackle the same base problem here), so we just let
			// the computer try out which split will balance the no-fly times induced by the N2 and the He
			// at best.

			// first of all, skip any optimization in case the current compartment is not the leading one
			if( (nofly_N2 <= int_O_nofly_time) && (nofly_He <= int_O_nofly_time) ) break;

			// check if the N2 requires more waiting time than the He
			if( nofly_N2 >= nofly_He )
			{
				// check if the search direction has changed, which means we are beyond the
				// optimum now, or if we are at the upper stop limit of split_N2_He
				if( (search_direction < 0) || (split_N2_He[ci] == 99) )
				{	
					// Either the just completed iteration was more close to the optimum or the one before
					// was, so we take the best (i.e. shortest) time of both as the final no-fly time.
					int_O_nofly_time = (nofly_N2 < nofly_last) ? nofly_N2 : nofly_last;
					break;
				}

				// store the no-fly time found in this iteration
				nofly_last = nofly_N2;

				// increase the N2 fraction of the split and set search direction towards more N2
				split_N2_He[ci]  +=  1;
				search_direction  = +1;
			}
			else
			{
				// check if the search direction has changed, which means we are beyond the
				// optimum now, or if we are at the lower stop limit of split_N2_He
				if( (search_direction > 0) || (split_N2_He[ci] == 1) )
				{
					// Either the just completed iteration was more close to the optimum or the one before
					// was, so we take the best (i.e. shortest) time of both as the final no-fly time.
					int_O_nofly_time = (nofly_He < nofly_last) ? nofly_He : nofly_last;
					break;
				}

				// store the no-fly time found in this iteration
				nofly_last = nofly_He;

				// decrease the N2 fraction of the split and set search direction towards less N2
				split_N2_He[ci]  -=  1;
				search_direction  = -1;	
			}

		} // for(;;)

	} // for(compartments)


	// Rescale int_O_desaturation_time and int_O_nofly_time to full minutes for display purpose
	int_O_desaturation_time *= 10;
	int_O_nofly_time		*= 10;

	// Limit int_O_desaturation_time and int_O_nofly_time to 5999 = 99 hours + 59 minutes
	// because of display space constraints and rounding done above.
	if( int_O_desaturation_time > 5999 ) int_O_desaturation_time = 5999;
	if( int_O_nofly_time        > 5999 ) int_O_nofly_time        = 5999;


	// Clear the microbubbles warning when the current gradient factor is < GF_WARNING_THRESHOLD.
	// As the locked warning will stay set, this will cause the warning be be displayed in attention
	// color instead of warning color.
	if( int_O_gradient_factor < GF_WARNING_THRESHOLD )
		char_O_deco_warnings &= ~DECO_WARNING_MBUBBLES;

	// clear some warnings when the desaturation time has become zero
	if( int_O_desaturation_time == 0 )
		char_O_deco_warnings &= ~(   DECO_WARNING_IBCD     + DECO_WARNING_IBCD_lock
		                           + DECO_WARNING_MBUBBLES + DECO_WARNING_MBUBBLES_lock
		                           + DECO_WARNING_OUTSIDE  + DECO_WARNING_OUTSIDE_lock  );

}

//////////////////////////////////////////////////////////////////////////////
// Calculate desaturation of the real tissues for a given time interval
//
// Caution: Works on the real tissues!
//          If in doubt, use this function only inside a context surrounded with
//          push_tissues_to_vault() / pull_tissues_from_vault() !
//
// Input:    int_I_pres_surface : surface pressure in mbar
//           time_interval      : time interval in minutes, must be limited to 254 at max
//
// Modified: tissue pressures
//           CNS value
//           ceiling and current GF
//
static void calc_interval(PARAMETER unsigned char time_interval)
{
	overlay unsigned char time;


	assert( 800 <  int_I_pres_surface             && int_I_pres_surface             <  1100 );
	assert( 100 <= char_I_saturation_multiplier   && char_I_saturation_multiplier   <  200  );
	assert( 0   <  char_I_desaturation_multiplier && char_I_desaturation_multiplier <= 100  );


	// setup input data for deco routines
	pres_respiration = pres_surface = 0.001 * int_I_pres_surface ;

	N2_ratio       = 0.7902;									// according to Buhlmann
	N2_equilibrium = N2_ratio * (pres_surface     - ppWater);	// used for N2 tissue graphics scaling
	ppN2           = N2_ratio * (pres_respiration - ppWater);
	ppHe           = 0.0;

	float_desaturation_multiplier = 0.01 * char_I_desaturation_multiplier * SURFACE_DESAT_FACTOR;
	float_saturation_multiplier   = 0.01 * char_I_saturation_multiplier;


	// Calculate the tissues:
	// Because calc_tissues() can calculate for 127 minutes at max,
	// the tissue updating may need to be done in two chunks.

	time = time_interval;

	// first chunk for the part exceeding 127 minutes
	if( time > 127)
	{
		// do a full 127 minutes on the real tissues
		tissue_increment = 127 | TISSUE_FLAG;
		calc_tissues();

		// determine the remaining part
		time -= 127;
	}

	// program the remaining part (or full part if not exceeding 127 minutes)
	tissue_increment = time | TISSUE_FLAG;

	// update the N2 and He pressures in the tissues for the remaining part of the time interval
	calc_tissues();


	// Calculate CNS:
	// To speed up things and because on most invocations of this code char_I_dive_interval
	// is a multiple of 10 minutes, we loop the loop-counter down using two speeds.

	time = time_interval;

	while ( time )
	{
		if( time > 9 )
		{
			CNS_fraction *= 0.925874712;	// Half-time = 90min -> 10 min: (1/2)^(1/9)
			time         -= 10;				// fast speed looping
		}
		else
		{
			CNS_fraction *= 0.992327946;	// Half-time = 90min ->  1 min: (1/2)^(1/90)
			time         -= 1;				// slow speed looping
		}
	}

	// compute integer copy of CNS value
	convert_CNS_for_display();


	// calculate ceiling (for a GF high of 100%) and gradient factor
	calc_limit(1.0);
}


//////////////////////////////////////////////////////////////////////////////
// calc_CNS_increment
//
// Input:  char_ppO2        : current ppO2 [decibars]
//         tissue_increment : time increment and tissue selector
//
// Output: CNS_fraction_inc : increment of the CNS value
//
void calc_CNS_increment(void)
{
	overlay float time_factor = 1.0;	// default is 2sec

	assert( char_ppO2 > 15 );

	// adjust time factor if minute-based stepping is commanded, mask out flag bit
	if( tissue_increment & TIME_MASK ) time_factor = 30.0 * (float)(tissue_increment & TIME_MASK);

	//------------------------------------------------------------------------
	// Don't increase CNS below 0.5 bar, but keep it steady.
	if      (char_ppO2 <  50) CNS_fraction_inc = 0;				// no CNS increase below 0.5 bar ppO2
	//------------------------------------------------------------------------
	// Below (and including) 1.60 bar
	else if (char_ppO2 <  61) CNS_fraction_inc = time_factor/(-533.07 * char_ppO2 + 54000.0);
	else if (char_ppO2 <  71) CNS_fraction_inc = time_factor/(-444.22 * char_ppO2 + 48600.0);
	else if (char_ppO2 <  81) CNS_fraction_inc = time_factor/(-355.38 * char_ppO2 + 42300.0);
	else if (char_ppO2 <  91) CNS_fraction_inc = time_factor/(-266.53 * char_ppO2 + 35100.0);
	else if (char_ppO2 < 111) CNS_fraction_inc = time_factor/(-177.69 * char_ppO2 + 27000.0);
	else if (char_ppO2 < 152) CNS_fraction_inc = time_factor/( -88.84 * char_ppO2 + 17100.0);
	else if (char_ppO2 < 167) CNS_fraction_inc = time_factor/(-222.11 * char_ppO2 + 37350.0);
	//------------------------------------------------------------------------
	// Arieli et all.(2002): Modeling pulmonary and CNS O2 toxicity:
	// J Appl Physiol 92: 248--256, 2002, doi:10.1152/japplphysiol.00434.2001
	// Formula (A1) based on value for 1.55 and c=20
	// example calculation: Sqrt((1.7/1.55)^20)*0.000404
	else if (char_ppO2 < 172) CNS_fraction_inc = time_factor*0.00102;
	else if (char_ppO2 < 177) CNS_fraction_inc = time_factor*0.00136;
	else if (char_ppO2 < 182) CNS_fraction_inc = time_factor*0.00180;
	else if (char_ppO2 < 187) CNS_fraction_inc = time_factor*0.00237;
	else if (char_ppO2 < 192) CNS_fraction_inc = time_factor*0.00310;
	else if (char_ppO2 < 198) CNS_fraction_inc = time_factor*0.00401;
	else if (char_ppO2 < 203) CNS_fraction_inc = time_factor*0.00517;
	else if (char_ppO2 < 233) CNS_fraction_inc = time_factor*0.0209;
	else                      CNS_fraction_inc = time_factor*0.0482; // value for 2.5 bar, used for 2.33 bar and above
}

//////////////////////////////////////////////////////////////////////////////
// calc_CNS_planning
//
// Compute CNS increase during predicted ascent
//
// Input:   internal_deco_time[], internal_deco_depth[], internal_deco_gas[]
// Output:  sim_CNS_fraction
//
void calc_CNS_planning(void)
{
	// null sim_CNS_fraction
	sim_CNS_fraction = 0.0;

	//---- CCR mode : do the full TTS at once ---------------------------------

	if( ((char_O_deco_status & DECO_MODE_MASK) == DECO_MODE_CCR) )
	{
		overlay unsigned short t;						// needs 16 bits here !

		// get current ppO2 from sensors or setpoint
		char_ppO2 = char_I_const_ppO2;

		// calculate CNS% for the period of additional staying at bottom depth (fTTS / delayed ascent)
		if( char_O_deco_status & DECO_ASCENT_DELAYED)
		{
			tissue_increment = char_I_extra_time;		// must be limited to 127, is limited by range of char_I_extra_time
			calc_CNS_increment();						// calculate the CNS increment
			sim_CNS_fraction += CNS_fraction_inc;		// sum up
		}

		// get the ascent time dependent on the current plan
		t = (char_O_deco_status & DECO_PLAN_ALTERNATE) ? int_O_alternate_ascenttime : int_O_ascenttime;

		// start simulating CNS% in chunks of 127 minutes
		tissue_increment = 127;

		while( t > 127 )
		{
			t -= 127;									// tissue_increment is limited to 127 minutes because of flag in bit 7
			calc_CNS_increment();						// calculate CNS in chunks of full 127 minutes
			sim_CNS_fraction += CNS_fraction_inc;		// sum up
		}

		tissue_increment = (char)t;						// get the remaining minutes <= 127
		calc_CNS_increment();							// calculate CNS for the remaining minutes
		sim_CNS_fraction += CNS_fraction_inc;			// sum up
	}
	else //---- OC mode and pSCR without sensors: have to follow all gas switches... -----
	{
		overlay float float_actual_ppO2;
		overlay float abs_pres;

		overlay unsigned char stop_depth;
		overlay unsigned char last_gas;
		overlay unsigned char i;						// stop table index


		// retrieve bottom gas: 1-5 for the configured gases or 0 for the manually set gas
		last_gas = sim_gas_last_used = sim_gas_first_used;

		// get the calc_N2/He/O2_ratios of the bottom gas
		gas_set_ratios();

		// calculate absolute pressure
		abs_pres = pres_surface + bottom_depth * METER_TO_BAR;

		// calculate OC ppO2 (ppWater omitted here on purpose)
		float_actual_ppO2 = abs_pres * sim_O2_ratio;

		// correct ppO2 in case of pSCR mode by drop
		if( char_O_deco_status & DECO_MODE_PSCR ) float_actual_ppO2 -= sim_pSCR_drop;

		// convert ppO2 from float to char
		if      ( float_actual_ppO2 < 0.0   ) char_ppO2 =   0;
		else if ( float_actual_ppO2 > 2.545 ) char_ppO2 = 255;
		else                                  char_ppO2 = (unsigned char)(100 * float_actual_ppO2 + 0.5);


		// simulate extended bottom time (fTTS) / delay before ascent (bailout) if configured
		if( char_O_deco_status & DECO_ASCENT_DELAYED )
		{
			tissue_increment  = char_I_extra_time;	// must be limited to 127, is limited by range of char_I_extra_time
			calc_CNS_increment();					// calculate the CNS increment
			sim_CNS_fraction += CNS_fraction_inc;	// sum up
		}


		// For simplicity reason (non-linearity of the relation between ppO2 and CNS increments), the
		// whole ascent is calculated with bottom ppO2. This errs, but it does so to the safe side.

		// calculate ascent time (integer division and generous round-up)
		tissue_increment = bottom_depth / char_I_ascent_speed + 1;

		// ** commented out - not needed when char_I_ascent_speed is limited to a minimum
		// **                 of 2.something, it is indeed limited to a minimum of 5.
		//
		// // limit tissue_increment to 127 minutes
		// if( tissue_increment > 127 ) tissue_increment = 127;

		// simulate the CNS increase
		calc_CNS_increment();						// calculate the CNS increment
		sim_CNS_fraction += CNS_fraction_inc;		// sum up


		//---- Stops ---------------------------------------------------------
		
		for(i=0; i<NUM_STOPS; ++i)
		{
			// get the depth of the stop
			stop_depth = internal_deco_depth[i];

			// did we reach the last entry (depth = 0)? if yes, done
			if (stop_depth == 0) break;

			// get the duration of the stop and the gas breathed
			tissue_increment  = internal_deco_time[i];
			sim_gas_last_used = internal_deco_gas[i];

			// do we have a gas switch?
			if( sim_gas_last_used != last_gas )
			{
				// yes - get new calculation ratios
				gas_set_ratios();

				// remember new gas as last gas
				last_gas = sim_gas_last_used;
			}

			// calculate absolute pressure at stop depth
			abs_pres = pres_surface + stop_depth * METER_TO_BAR;

			// calculate OC ppO2 (ppWater omitted here on purpose)
			float_actual_ppO2 = abs_pres * sim_O2_ratio;

			// correct ppO2 in case of pSCR mode by drop
			if( char_O_deco_status & DECO_MODE_PSCR ) float_actual_ppO2 -= sim_pSCR_drop;

			// convert ppO2 from float to char
			if      ( float_actual_ppO2 < 0.0   ) char_ppO2 =   0;
			else if ( float_actual_ppO2 > 2.545 ) char_ppO2 = 255;
			else                                  char_ppO2 = (unsigned char)(100 * float_actual_ppO2 + 0.5);

			// ** Currently, stop times per stop entry are limited to 99 minutes in update_deco_table(),
			// ** so the following code block is not needed at times.
			//
			// // tissue_increment is limited to 127 when fed to calc_CNS_increment(),
			// // so if the stop is longer than 127 minutes (but not longer than 254 minutes!)
			// // we need to calculate the CNS in two chunks.
			// if( tissue_increment > 127)
			// {
			//		tissue_increment -= 127;				// subtract full 127 minutes and do the "remaining" minutes first
			//		calc_CNS_increment();					// calculate the CNS increment
			//		sim_CNS_fraction += CNS_fraction_inc;	// sum up
			//		tissue_increment  = 127;				// catch up with the previously subtracted full 127 minutes
			// }

			// calculate CNS% for the stop
			calc_CNS_increment();					// calculate the CNS increment
			sim_CNS_fraction += CNS_fraction_inc;	// sum up
		}
	}
}


//////////////////////////////////////////////////////////////////////////////
// gas_volumes
//
// calculates volumes and required tank fill pressures for each gas.
//
// Input:	bottom_depth			depth of the bottom segment
//			char_I_bottom_time		duration of the bottom segment
//			char_I_extra_time		extra bottom time for fTTS / delayed ascent
//			float_ascent_speed		ascent speed, in meters/minute
//			sim_gas_first_used		the bottom gas (1-5 for configured gases, 0 for the manual gas)
//			internal_deco_depth[]	depth of the stops
//			internal_deco_time[]	duration of the stops
//			internal_deco_gas[]		gas breathed at the stops
//			char_I_bottom_usage		gas consumption during bottom part and initial ascent, in liters/minute
//			char_I_deco_usage		gas consumption during stops and following ascents, in liters/minute
//			char_I_tank_size[]		size of the tanks for gas 1-5, in liters
//			char_I_tank_pres_fill[]	fill pressure of the tanks
//
// Output:	int_O_gas_volumes[]		amount of gas needed, in liters
//			int_O_tank_pres_need[]	in bar, + flags for fast evaluation by dive mode warnings:
//											  2^15: pres_need >= pres_fill
//											  2^14: pres_need >= press_fill * GAS_NEEDS_ATTENTION_THRESHOLD
//											  2^11: pres_need == 0
//											  2^10: pres_need invalid
//
void gas_volumes_helper(void)
{
	// Calculate the gas volume needed at a given depth, time and usage (SAC rate).
	// We use 1.0 for the surface pressure to have stable results when used through
	// the deco calculator (simulation mode).
	volume = (float_depth * METER_TO_BAR + 1.0) * float_time * usage;

	return;
}

void gas_volumes(void)
{
	overlay float volumes[NUM_GAS];

	overlay unsigned char stop_gas;
	overlay unsigned char stop_gas_last;
	overlay unsigned char stop_time;
	overlay unsigned char stop_depth;
	overlay unsigned char stop_depth_last;
	overlay unsigned char i;


	//---- initialization ----------------------------------------------------

	// null the volume accumulators
	for(i=0; i<NUM_GAS; ++i) volumes[i] = 0.0;

	// quit for CCR and pSCR mode
	if( char_O_deco_status & DECO_MODE_LOOP ) goto done;


	//---- bottom demand -----------------------------------------------------

	// sim_gas_first_used : gas used during bottom segment (0, 1-5)
	// bottom_depth: depth of the bottom segment

	assert(0 <= sim_gas_first_used && sim_gas_first_used <= NUM_GAS);

	// get the gas used during bottom segment
	stop_gas_last = stop_gas = sim_gas_first_used;

	// set the usage (SAC rate) to bottom usage rate for bottom part and initial ascent
	usage = char_I_bottom_usage;

	// volumes are only calculated for gases 1-5, but not the manually configured one
	if( stop_gas )
	{
		// set the bottom depth
		float_depth = (float)bottom_depth;

		// calculate either bottom segment or just the fTTS/bailout delayed part
		if( char_O_main_status & DECO_BOTTOM_CALCULATE )
		{
			// duration of bottom segment
			float_time = (float)char_I_bottom_time;
		}
		else
		{
			// duration of delayed ascent
			float_time = (float)char_I_extra_time;
		}

		// calculate gas demand
		gas_volumes_helper();

		// take result
		volumes[stop_gas-1] = volume;
	}

	// initialize stop index with first stop
	i = 0;

	//---- initial ascent demand ---------------------------------------------

	// stop_gas                : gas from bottom segment
	// bottom_depth            : depth of the bottom segment
	// internal_deco_depth[i=0]: depth of the first stop, may be 0 if no stop exists

	// get the data of the first stop	
	stop_depth = internal_deco_depth[i];
	stop_time  = internal_deco_time[i];

	// volumes are only calculated for gases 1-5, but not the manually configured one
	if( stop_gas )
	{
		// compute distance between bottom and first stop
		float_depth = (float)bottom_depth - (float)stop_depth;

		// initial ascent exists only if ascent distance is > 0
		if( float_depth > 0.0 )
		{
			// compute ascent time
			float_time = float_depth / float_ascent_speed;

			// compute average depth between bottom and first stop
			float_depth = (float)bottom_depth - float_depth * 0.5;

			// calculate gas demand
			gas_volumes_helper();

			// add result
			volumes[stop_gas-1] += volume;
		}
	}

	// switch the usage (SAC rate) to deco usage rate
	// for stops, intermediate and final ascent
	usage = char_I_deco_usage;

	// is there a (first) stop? if yes, goto stops processing
	if( stop_depth ) goto stops;

	// add demand of a 3 minutes safety stop at 5 meters, at least for contingency...
	float_time  = 3.0;
	float_depth = 5.0;

	// calculate gas demand
	gas_volumes_helper();

	// add result
	volumes[stop_gas-1] += volume;

	// proceed to volume conversion and pressure calculations
	goto done;


	//---- intermediate ascent demand ---------------------------------------
inter_ascents:

	// store last stop depth and gas
	stop_depth_last = stop_depth;
	stop_gas_last   = stop_gas;

	// check if we are at the end of the stops table
	if( i < NUM_STOPS-1 )
	{
		// there are more entries - get the next stop data
		i++;

		// get the next stop depth
		stop_depth = internal_deco_depth[i];

		// check if there is indeed another stop,
		// if not (depth = 0) treat as end of table
		if( stop_depth == 0 ) goto end_of_table;

		// get the next stop duration
		stop_time = internal_deco_time[i];
	}
	else
	{
end_of_table:

		// End of the stops table reached or no more stops: Split the remaining
		// ascent into an intermediate ascent and a final ascent by creating a
		// dummy stop at the usual last deco stop depth. Stop gas doesn't change.
		stop_time  = 0;
		stop_depth = char_I_depth_last_deco;
	}

	// volumes are only calculated for gases 1-5, but not the manually configured one
	if( stop_gas_last )
	{
		// compute distance between the two stops:
		// last stop will always be deeper than current stop
		float_depth = (float)(stop_depth_last - stop_depth);

		// compute ascent time
		float_time = float_depth / float_ascent_speed;

		// compute average depth between the two stops
		float_depth = (float)stop_depth_last - float_depth * 0.5;

		// calculate gas demand
		gas_volumes_helper();

		// add result
		volumes[stop_gas_last-1] += volume;
	}


	//---- next stop demand -------------------------------------------------
stops:

	// convert depth of the stop
	float_depth = (float)stop_depth;

	// get the next gas
	stop_gas = internal_deco_gas[i];

	// do we we have a gas change?
	if( stop_gas_last && (stop_gas != stop_gas_last) )
	{
		// yes - spend an additional char_I_gas_change_time on the old gas
		float_time = (float)char_I_gas_change_time;

		// calculate gas demand
		gas_volumes_helper();

		// add result
		volumes[stop_gas_last-1] += volume;
	}

	// calculate and add demand on new gas for the full stop duration
	if( stop_gas )
	{
		// get the duration of the stop
		float_time = (float)stop_time;

		// calculate gas demand
		gas_volumes_helper();

		// add result to last gas
		volumes[stop_gas-1] += volume;
	}

	// continue with the next intermediate ascent if this was not the last stop
	if( stop_depth > char_I_depth_last_deco ) goto inter_ascents;


	//---- final ascent demand -----------------------------------------------
final_ascent:

	// float_depth: depth of last stop
	// stop_gas   : gas from last stop (0 or 1-5)

	// volumes are only calculated for gases 1-5, but not the manually configured one	
	if( stop_gas )
	{
		// set ascent time according to an ascent speed of 1 meter per minute
		float_time = float_depth;

		// set half-way depth
		float_depth *= 0.5;

		// calculate gas demand
		gas_volumes_helper();

		// add result
		volumes[stop_gas-1] += volume;
	}


	//---- convert results for the assembler interface -----------------------------
done:

	for(i=0; i<NUM_GAS; ++i)
	{
		if( volumes[i] >= 65534.5 )
		{
			int_O_gas_volumes[i]	= 65535;
			int_O_tank_pres_need[i]	= 999 + INT_FLAG_WARNING; // 999 bar + warning flag for > pres_fill
		}
		else
		{
			overlay unsigned short tank_pres_fill = 10.0 * (unsigned short)char_I_tank_pres_fill[i];

			// No distinct rounding done here because volumes are not accurate to the single liter anyhow

			// convert gas volumes to integers
			int_O_gas_volumes[i]     = (unsigned short)volumes[i];

			// compute how much pressure in the tank will be needed [in bar]  (integer-division)
			int_O_tank_pres_need[i]  = (unsigned short)(int_O_gas_volumes[i] / char_I_tank_size[i]);

			// limit to 999 bar because of display constraints
			if( int_O_tank_pres_need[i] > 999 ) int_O_tank_pres_need[i] = 999;

			// set flags for fast evaluation by divemode check for warnings
			if     ( int_O_tank_pres_need[i] == 0 )
			{
				// set flag for 0 bar
				int_O_tank_pres_need[i] |= INT_FLAG_ZERO;
			}
			else if( int_O_tank_pres_need[i] >= tank_pres_fill )
			{
				// set warning flag
				int_O_tank_pres_need[i] |= INT_FLAG_WARNING;

			}
			else if( int_O_tank_pres_need[i] >= tank_pres_fill * GAS_NEEDS_ATTENTION_THRESHOLD )
			{
				// set pre-warning flag
				int_O_tank_pres_need[i] |= INT_FLAG_ATTENTION;
			}

			// set invalid flag if there is an overflow in the stops table
			if( char_O_deco_warnings & DECO_WARNING_STOPTABLE_OVERFLOW )
				int_O_tank_pres_need[i] |= INT_FLAG_INVALID;
			
		} // if( volumes[i] )
	} // for
}

//////////////////////////////////////////////////////////////////////////////

void convert_CNS_for_display(void)
{
	if		( CNS_fraction <  0.01  ) int_O_CNS_fraction = 0;
	else if ( CNS_fraction >= 9.985 ) int_O_CNS_fraction = 999 + INT_FLAG_WARNING;
	else
	{
		// convert float to integer
		int_O_CNS_fraction = (unsigned short)(100 * CNS_fraction + 0.5);

		// set warnings
		if		( int_O_CNS_fraction >= CNS_WARNING_THRESHOLD   ) int_O_CNS_fraction |= INT_FLAG_WARNING;
		else if	( int_O_CNS_fraction >= CNS_ATTENTION_THRESHOLD ) int_O_CNS_fraction |= INT_FLAG_ATTENTION;
	}
}

//////////////////////////////////////////////////////////////////////////////

void convert_sim_CNS_for_display(void)
{
	if		( sim_CNS_fraction <  0.01  ) int_sim_CNS_fraction = 0;
	else if ( sim_CNS_fraction >= 9.985 ) int_sim_CNS_fraction = 999 + INT_FLAG_WARNING;
	else
	{
		// convert float to integer
		int_sim_CNS_fraction = (unsigned short)(100 * sim_CNS_fraction + 0.5);

		// set warning flag if CNS is >= 100%
		if      ( int_sim_CNS_fraction >= CNS_WARNING_THRESHOLD    ) int_sim_CNS_fraction |= INT_FLAG_WARNING;
		else if ( int_sim_CNS_fraction >= CNS_ATTENTION_THRESHOLD  ) int_sim_CNS_fraction |= INT_FLAG_ATTENTION;

		// set invalid flag if there is an overflow in the stops table
		if( char_O_deco_warnings & DECO_WARNING_STOPTABLE_OVERFLOW ) int_sim_CNS_fraction |= INT_FLAG_INVALID;
	}
}

//////////////////////////////////////////////////////////////////////////////
// push_tissues_to_vault & pull_tissues_from_vault
//
// ATTENTION: Do not use from inside the deco engine!
//            The vault is exclusively reserved to back-up and restore the real
//            tissues and related data when entering / leaving simulation mode!
//

void push_tissues_to_vault(void)
{
	overlay unsigned char x;

	cns_vault_float      = CNS_fraction;
	deco_warnings_vault  = char_O_deco_warnings;

	for (x=0;x<NUM_COMP;x++)
	{
		pres_tissue_N2_vault[x] = pres_tissue_N2[x];
		pres_tissue_He_vault[x] = pres_tissue_He[x];
	}
}

void pull_tissues_from_vault(void)
{
	overlay unsigned char x;

	CNS_fraction         = cns_vault_float;
	char_O_deco_warnings = deco_warnings_vault;

	convert_CNS_for_display();

	locked_GF_step_norm  = GF_delta / low_depth_norm;
	locked_GF_step_alt   = GF_delta / low_depth_alt;
	
	for (x=0; x<NUM_COMP; x++)
	{
		pres_tissue_N2[x] = pres_tissue_N2_vault[x];
		pres_tissue_He[x] = pres_tissue_He_vault[x];
	}
}

//////////////////////////////////////////////////////////////////////////////
//
#ifndef CROSS_COMPILE
void main() {}
#endif