Planche Math
hey i was at the beach today looking over my workout logs thinking of my lines that describe my planche workouts compared to bench press. we both know planche progress is hard to measure, today i did something about it by doing the math.
The Setup
the first thing about my planche training is i use bands, so the calculation starts with the following inputs:
pullUpBarHeight, // height of pull up bar from ground in meters
handPosition = 0, // distance from hands to bar (horizontally) in meters
bodyHeightMeters, // height from floor in planche position in meters
bandRestLength = 1.04, // original length of band in meters
minResistance, // minimum resistance in kg
maxResistance, // maximum resistance in kg
maxStretch = 2.5, // maximum recommended stretch multiplier
i use rubberbanditz which publishes the minimum band resistance and maximum band resistance, i track how high the bar is and looking at videos of my effort i figure my body height.
the horizontal hand position is used to calculate the stretch of the band using pythagorean theorem.
The Scoring System
with the resistance of the band calculated then its time to calculate a score, which is where some heuristics come in. first, weighting the progressions:
tuck: 25, // 25% of full planche difficulty
advTuck: 40, // 40% of full planche difficulty
singleLeg: 70, // 70% of full planche difficulty
straddle: 70, // 70% of full planche difficulty
full: 100, // 100% - perfect form
then 3 second hold is the minimum and 10 second hold is perfect.
an assistance multiplier is calculated by taking the body weight and the resistance of the band into account.
and a hold time multiplier is calculated by comparing the actual hold time to the minimum and perfect hold times.
the final score is a factor of the position, assistance, and hold time. and that final score gets connected to some semantic levels:
if (score >= 95) return 'Master';
if (score >= 80) return 'Advanced';
if (score >= 60) return 'Intermediate';
if (score >= 40) return 'Beginner-Intermediate';
My Current Level
so for my workout with a #3 (black) rubberbanditz band, bar at 2.25 meters height, my legs in straddle position and a hold time of 5 seconds the score is 44, "Beginner-Intermediate" level.
which feels about right given a full planche with no band for 10 seconds is a score of 100.
The Full Code
here's code with the details of the calculations:
type BandSpecs = {
layFlatLengthInches: number;
layFlatLengthMeters: number;
bandName: string;
bandNumber: number;
widthInches: number;
minResistanceLb: number;
maxResistanceLb: number;
minResistanceKg: number;
maxResistanceKg: number;
maxStretchMultiplier: number;
};
export const lightBand: BandSpecs = {
bandName: 'light',
layFlatLengthInches: 41,
layFlatLengthMeters: 1.04,
bandNumber: 1,
widthInches: 0.25,
minResistanceLb: 5,
maxResistanceLb: 15,
minResistanceKg: 2,
maxResistanceKg: 7,
maxStretchMultiplier: 2.5,
};
export const mediumBand: BandSpecs = {
bandName: 'medium',
layFlatLengthInches: 41,
layFlatLengthMeters: 1.04,
bandNumber: 2,
widthInches: 0.5,
minResistanceLb: 20,
maxResistanceLb: 30,
minResistanceKg: 9,
maxResistanceKg: 16,
maxStretchMultiplier: 2.5,
};
export const heavyBand: BandSpecs = {
bandName: 'heavy',
layFlatLengthInches: 41,
layFlatLengthMeters: 1.04,
bandNumber: 3,
widthInches: 0.75,
minResistanceLb: 30,
maxResistanceLb: 50,
minResistanceKg: 14,
maxResistanceKg: 23,
maxStretchMultiplier: 2.5,
};
export const robustBand: BandSpecs = {
bandName: 'robust',
layFlatLengthInches: 41,
layFlatLengthMeters: 1.04,
bandNumber: 4,
widthInches: 1.125,
minResistanceLb: 40,
maxResistanceLb: 80,
minResistanceKg: 18,
maxResistanceKg: 36,
maxStretchMultiplier: 2.5,
};
export const powerBand: BandSpecs = {
bandName: 'power',
layFlatLengthInches: 41,
layFlatLengthMeters: 1.04,
bandNumber: 5,
widthInches: 1.75,
minResistanceLb: 50,
maxResistanceLb: 120,
minResistanceKg: 23,
maxResistanceKg: 55,
maxStretchMultiplier: 2.5,
};
export const strongBand: BandSpecs = {
bandName: 'strong',
layFlatLengthInches: 41,
layFlatLengthMeters: 1.04,
bandNumber: 6,
widthInches: 2.5,
minResistanceLb: 60,
maxResistanceLb: 150,
minResistanceKg: 27,
maxResistanceKg: 68,
maxStretchMultiplier: 2.5,
};
export const monsterBand: BandSpecs = {
bandName: 'monster',
layFlatLengthInches: 41,
layFlatLengthMeters: 1.04,
bandNumber: 8,
widthInches: 4,
minResistanceLb: 80,
maxResistanceLb: 200,
minResistanceKg: 36,
maxResistanceKg: 91,
maxStretchMultiplier: 2.5,
};
export const BAND_SPECS: BandSpecs[] = [
lightBand,
mediumBand,
heavyBand,
robustBand,
powerBand,
strongBand,
monsterBand,
];
export function calculatePlancheBandResistance({
pullUpBarHeight, // height of pull up bar from ground in meters
handPosition = 0, // distance from hands to bar (horizontally) in meters
bodyHeightMeters, // height from floor in planche position in meters
bandRestLength = 1.04, // original length of band in meters
minResistance, // minimum resistance in kg
maxResistance, // maximum resistance in kg
maxStretch = 2.5, // maximum recommended stretch multiplier
}) {
// Calculate the actual stretched length using Pythagorean theorem
const stretchedLength = Math.sqrt(
Math.pow(pullUpBarHeight - bodyHeightMeters, 2) + Math.pow(handPosition, 2)
);
// Calculate stretch ratio
const stretchRatio = stretchedLength / bandRestLength;
if (stretchRatio > maxStretch) {
throw new Error('Band is stretched beyond safe limits');
}
// Quadratic resistance curve for more realistic resistance calculation
const stretchPercentage = (stretchRatio - 1) / (maxStretch - 1);
const resistance =
minResistance +
(maxResistance - minResistance) * Math.pow(stretchPercentage, 2);
return {
resistance: Math.round(resistance * 10) / 10,
stretchedLength: Math.round(stretchedLength * 100) / 100,
stretchRatio: Math.round(stretchRatio * 100) / 100,
isInSafeRange: stretchRatio <= maxStretch,
};
}
export const PROGRESSION_WEIGHTS = {
tuck: 25, // 25% of full planche difficulty
advTuck: 40, // 40% of full planche difficulty
singleLeg: 70, // 70% of full planche difficulty
straddle: 70, // 70% of full planche difficulty
full: 100, // 100% - perfect form
} as const;
export function calculatePlancheScore({
position, // tuck, advTuck, singleLeg, straddle, full
bodyWeightKg, // in kg
bandResistanceKg = 0, // in kg (0 if no band)
holdTime = 0, // in seconds
minHoldTime = 3, // minimum seconds for valid hold
perfectHoldTime = 10, // target hold time for max score
}: {
position: keyof typeof PROGRESSION_WEIGHTS;
bodyWeightKg: number;
bandResistanceKg: number;
holdTime: number;
minHoldTime?: number;
perfectHoldTime?: number;
}) {
// Validate position
if (!PROGRESSION_WEIGHTS[position]) {
throw new Error('Invalid position');
}
// Calculate effective weight (body weight - band assistance)
const effectiveWeight = bodyWeightKg - bandResistanceKg;
const weightPercentage = (effectiveWeight / bodyWeightKg) * 100;
// Calculate base score from position
const positionScore = PROGRESSION_WEIGHTS[position];
// Calculate hold time multiplier (0.7-1.0)
const holdTimeMultiplier =
holdTime < minHoldTime
? 0
: Math.min(1, 0.7 + 0.3 * (holdTime / perfectHoldTime));
// Calculate weight assistance penalty
const assistanceMultiplier = weightPercentage / 100;
// Calculate final score (0-100)
const finalScore = positionScore * assistanceMultiplier * holdTimeMultiplier;
return {
score: Math.round(finalScore),
details: {
positionScore,
weightPercentage: Math.round(weightPercentage),
holdTimeMultiplier: Math.round(holdTimeMultiplier * 100) / 100,
assistanceMultiplier: Math.round(assistanceMultiplier * 100) / 100,
},
level: getPlancheLevel(finalScore),
};
}
function getPlancheLevel(score: number) {
if (score >= 95) return 'Master';
if (score >= 80) return 'Advanced';
if (score >= 60) return 'Intermediate';
if (score >= 40) return 'Beginner-Intermediate';
return 'Beginner';
}
const resistance = calculatePlancheBandResistance({
bodyHeightMeters: 0.55,
pullUpBarHeight: 2.25,
minResistance: heavyBand.minResistanceKg,
maxResistance: heavyBand.maxResistanceKg,
});
const score = calculatePlancheScore({
bandResistanceKg: resistance.resistance,
position: 'straddle',
bodyWeightKg: 61.235,
holdTime: 5,
});
console.log(score);
// {
// score: 44,
// details: {
// positionScore: 70,
// weightPercentage: 75,
// holdTimeMultiplier: 0.85,
// assistanceMultiplier: 0.75
// },
// level: 'Beginner-Intermediate'
// }
THAT WAS HARD!